From 70e80ff8e52cc1907756e59ba0a14d0d9cbc7112 Mon Sep 17 00:00:00 2001 From: ncsa-mo Date: Tue, 14 Nov 2017 16:15:38 -0600 Subject: [PATCH 01/22] add --- mdf_forge/forge.py | 71 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/mdf_forge/forge.py b/mdf_forge/forge.py index 63adac6..c874ffe 100644 --- a/mdf_forge/forge.py +++ b/mdf_forge/forge.py @@ -439,6 +439,54 @@ def match_titles(self, titles): return self + def match_years(self, years, match_all=True): + """Add years and limits to the query. + Options: + match_year(2005) + match_year([2005, 2010]) either 2005 or 2010 + match_year(min=1999, max=2005), if no min everything up to the year, if no max everything up to now + + Arguments: + years (str or list of str): The years or range to match. + + Returns: + self (Forge): For chaining. + """ + if not years: + return self + if not isinstance(years, list): + years = [years] + + year_start = "*"; year_stop = "*" + for year in years: + if year.isdigit() and len(year) == 4: + years_new = years_new.append(year) + elif "min=" in year and year[4:4].isdigit() and len(year) == 8: + year_start = year[4:4] + elif "max=" in year and year[4:4].isdigit() and len(year) == 8: + year_stop = year[4:4] + else: + print("A year is not a valid input of 'yyyy', 'min=yyyy', or 'max=yyyy' types") + return self + + if year_start == "*" and year_stop == "*": + self.match_field(field="mdf.year", value=years_new[0], required=True, new_group=True) + for year in years_new[1:]: + self.match_field(field="mdf.year", value=year, required=match_all, + new_group=False) + else: + self.match_range(self, field="mdf.year", start=year_start, stop=year_stop, inclusive=match_all, required=True, + new_group=False) + return self + + + def valid_year(year): + if year and year.isdigit(): + year = int(year) + if year >= 1900 and year <= 2020: + return year + + def match_resource_types(self, types): """Match the given resource types. @@ -536,6 +584,29 @@ def search_by_tags(self, tags, limit=None, match_all=True, info=False): return self.match_tags(tags, match_all=match_all).search(limit=limit, info=info) + def search_by_years(self, years, limit=None, match_all=True, info=False): + """Execute a search for the given year or years. + search_by_years([x]) is equivalent to match_years([x]).search() + + Arguments: + years (list of str): The years to match. Default []. + limit (int): The maximum number of results to return. + The max for this argument is the SEARCH_LIMIT imposed by Globus Search. + match_all (bool): If True, will add elements with AND. + If False, will use OR. + Default True. + info (bool): If False, search will return a list of the results. + If True, search will return a tuple containing the results list, + and other information about the query. + Default False. + + Returns: + list (if info=False): The results. + tuple (if info=True): The results, and a dictionary of query information. + """ + return self.match_year(years, match_all=match_all).search(limit=limit, info=info) + + def aggregate_source(self, sources): """Aggregate all records from a given source. There is no limit to the number of results returned. From ac686a5cd8b2f4e7383896465f452d257002155e Mon Sep 17 00:00:00 2001 From: ncsa-mo Date: Tue, 14 Nov 2017 16:17:22 -0600 Subject: [PATCH 02/22] add match_years and helper --- mdf_forge/forge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mdf_forge/forge.py b/mdf_forge/forge.py index c874ffe..84fc120 100644 --- a/mdf_forge/forge.py +++ b/mdf_forge/forge.py @@ -447,7 +447,7 @@ def match_years(self, years, match_all=True): match_year(min=1999, max=2005), if no min everything up to the year, if no max everything up to now Arguments: - years (str or list of str): The years or range to match. + years (int, str or list of int or str): The years or range to match. Returns: self (Forge): For chaining. From 332bf8b0b272ff8678da6b937d8397683bbf4b52 Mon Sep 17 00:00:00 2001 From: ncsa-mo Date: Tue, 14 Nov 2017 16:37:58 -0600 Subject: [PATCH 03/22] add match_years tests --- mdf_forge/forge.py | 10 +++++++--- tests/test_forge.py | 32 +++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/mdf_forge/forge.py b/mdf_forge/forge.py index 84fc120..66d07cf 100644 --- a/mdf_forge/forge.py +++ b/mdf_forge/forge.py @@ -448,7 +448,10 @@ def match_years(self, years, match_all=True): Arguments: years (int, str or list of int or str): The years or range to match. - + match_all (bool) of 1) list of years: If True, will add with AND. If False, will use OR. + 2) min, max range: If True, the start and stop values will be included in the search. If False, + neither of them will be included. + Default True. Returns: self (Forge): For chaining. """ @@ -459,14 +462,15 @@ def match_years(self, years, match_all=True): year_start = "*"; year_stop = "*" for year in years: + year = str(year) if year.isdigit() and len(year) == 4: - years_new = years_new.append(year) + years_new = years_new.append(str(year)) elif "min=" in year and year[4:4].isdigit() and len(year) == 8: year_start = year[4:4] elif "max=" in year and year[4:4].isdigit() and len(year) == 8: year_stop = year[4:4] else: - print("A year is not a valid input of 'yyyy', 'min=yyyy', or 'max=yyyy' types") + print("A year is not a valid input of 'yyyy', 'min=yyyy', or 'max=yyyy' type") return self if year_start == "*" and year_stop == "*": diff --git a/tests/test_forge.py b/tests/test_forge.py index 0f7a5a0..a1efe1c 100644 --- a/tests/test_forge.py +++ b/tests/test_forge.py @@ -211,7 +211,8 @@ def check_field(res, field, value): "mdf.mdf_id", "mdf.resource_type", "mdf.title", - "mdf.tags" + "mdf.tags", + "mdf.year" ] if field not in supported_fields: raise ValueError("Implement or re-spell " @@ -242,6 +243,8 @@ def check_field(res, field, value): vals = r["mdf"]["tags"] except KeyError: vals = [] + elif field == "mdf.year": + vals = [r["mdf"]["year"]] # If a result does not contain the value, no match if value not in vals: @@ -465,6 +468,21 @@ def test_forge_match_tags(): assert f4.match_tags("") == f4 +def test_forge_match_years(): + # One year of data/results + f1 = forge.Forge() + years1 = "2015" + res1 = f1.match_years(years1).search() + assert res1 != [] + assert check_field(res1, "mdf.year", "2015") == 0 + + # Multiple years + f2 = forge.Forge() + years2 = ["2015","2011"] + res2 = f2.match_years(years2, match_all=True).search() + assert check_field(res2, "mdf.years", "2011") == 2 + + def test_forge_match_resource_types(): f1 = forge.Forge() # Test one type @@ -557,6 +575,18 @@ def test_forge_search_by_tags(): assert all([r in res3 for r in res2]) +def test_forge_search_by_years(): + f1 = forge.Forge() + years1 = ["20111"] + res1 = f1.search_by_years(years1) + assert check_field(res1, "mdf.year", "2011") == 0 + + f2 = forge.Forge() + years2 = ["2020"] + res2 = f2.search_by_years(years2) + assert check_field(res2, "mdf.year", "2020") == 2 + + def test_forge_aggregate_source(): # Test limit f1 = forge.Forge() From ff7c4495ba6cbfc8b2dd2ee239e1877a621c91d4 Mon Sep 17 00:00:00 2001 From: ncsa-mo Date: Wed, 15 Nov 2017 17:02:25 -0600 Subject: [PATCH 04/22] test functions with min and max, correct string slice --- mdf_forge/forge.py | 24 +++++++++--------------- setup.cfg | 3 +-- tests/test_forge.py | 31 +++++++++++++++++++------------ 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/mdf_forge/forge.py b/mdf_forge/forge.py index 66d07cf..d6e3daa 100644 --- a/mdf_forge/forge.py +++ b/mdf_forge/forge.py @@ -461,16 +461,17 @@ def match_years(self, years, match_all=True): years = [years] year_start = "*"; year_stop = "*" + years_new = [] for year in years: year = str(year) if year.isdigit() and len(year) == 4: - years_new = years_new.append(str(year)) - elif "min=" in year and year[4:4].isdigit() and len(year) == 8: - year_start = year[4:4] - elif "max=" in year and year[4:4].isdigit() and len(year) == 8: - year_stop = year[4:4] + years_new.append(str(year)) + elif "min=" in year and year[4:8].isdigit() and len(year) == 8: + year_start = year[4:8] + elif "max=" in year and year[4:8].isdigit() and len(year) == 8: + year_stop = year[4:8] else: - print("A year is not a valid input of 'yyyy', 'min=yyyy', or 'max=yyyy' type") + print("Year is not a valid input; use 'yyyy', 'min=yyyy', or 'max=yyyy'.") return self if year_start == "*" and year_stop == "*": @@ -479,18 +480,11 @@ def match_years(self, years, match_all=True): self.match_field(field="mdf.year", value=year, required=match_all, new_group=False) else: - self.match_range(self, field="mdf.year", start=year_start, stop=year_stop, inclusive=match_all, required=True, + self.match_range(field="mdf.year", start=year_start, stop=year_stop, inclusive=match_all, required=True, new_group=False) return self - def valid_year(year): - if year and year.isdigit(): - year = int(year) - if year >= 1900 and year <= 2020: - return year - - def match_resource_types(self, types): """Match the given resource types. @@ -608,7 +602,7 @@ def search_by_years(self, years, limit=None, match_all=True, info=False): list (if info=False): The results. tuple (if info=True): The results, and a dictionary of query information. """ - return self.match_year(years, match_all=match_all).search(limit=limit, info=info) + return self.match_years(years, match_all=match_all).search(limit=limit, info=info) def aggregate_source(self, sources): diff --git a/setup.cfg b/setup.cfg index 87d6b83..2fd43d7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,5 +3,4 @@ universal=1 [tool:pytest] -addopts = --ignore=setup.py --cov=mdf_forge - +addopts = --ignore=setup.py --cov=mdf_forge \ No newline at end of file diff --git a/tests/test_forge.py b/tests/test_forge.py index a1efe1c..cccdbb1 100644 --- a/tests/test_forge.py +++ b/tests/test_forge.py @@ -468,19 +468,28 @@ def test_forge_match_tags(): assert f4.match_tags("") == f4 -def test_forge_match_years(): +def test_forge_match_years(capfd): # One year of data/results f1 = forge.Forge() years1 = "2015" res1 = f1.match_years(years1).search() assert res1 != [] - assert check_field(res1, "mdf.year", "2015") == 0 + assert check_field(res1, "mdf.year", 2015) == 0 # Multiple years f2 = forge.Forge() - years2 = ["2015","2011"] - res2 = f2.match_years(years2, match_all=True).search() - assert check_field(res2, "mdf.years", "2011") == 2 + years2 = [2015,"2011"] + # match_all=False (2011 OR 2015) + res2 = f2.match_years(years2, match_all=False).search() + assert check_field(res2, "mdf.year", 2011) == 2 + + # Wrong input + f3 = forge.Forge() + years3 = ["20x5"] + res3 = f3.match_years(years3, match_all=False).search() + out, err = capfd.readouterr() + msg_err = "Year is not a valid input" in out + assert msg_err == True def test_forge_match_resource_types(): @@ -577,14 +586,12 @@ def test_forge_search_by_tags(): def test_forge_search_by_years(): f1 = forge.Forge() - years1 = ["20111"] + years1 = ["min=2015","max=2016"] res1 = f1.search_by_years(years1) - assert check_field(res1, "mdf.year", "2011") == 0 - - f2 = forge.Forge() - years2 = ["2020"] - res2 = f2.search_by_years(years2) - assert check_field(res2, "mdf.year", "2020") == 2 + r1 = check_field(res1, "mdf.year", 2015) == 2 + r2 = check_field(res1, "mdf.year", 2016) == 2 + r3 = check_field(res1, "mdf.year", 2017) == -1 + assert all(r==True for r in [r1, r2, r3]) def test_forge_aggregate_source(): From 48a9443966f309c6907e768da5410923b5bd5284 Mon Sep 17 00:00:00 2001 From: ncsa-mo Date: Wed, 15 Nov 2017 17:03:48 -0600 Subject: [PATCH 05/22] removed comments --- mdf_forge/forge.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mdf_forge/forge.py b/mdf_forge/forge.py index d6e3daa..10753ca 100644 --- a/mdf_forge/forge.py +++ b/mdf_forge/forge.py @@ -441,10 +441,6 @@ def match_titles(self, titles): def match_years(self, years, match_all=True): """Add years and limits to the query. - Options: - match_year(2005) - match_year([2005, 2010]) either 2005 or 2010 - match_year(min=1999, max=2005), if no min everything up to the year, if no max everything up to now Arguments: years (int, str or list of int or str): The years or range to match. From 86b272f45502ea57a33f798f4416fd158a95c0c8 Mon Sep 17 00:00:00 2001 From: ncsa-mo Date: Thu, 16 Nov 2017 14:51:23 -0600 Subject: [PATCH 06/22] changed min, max to arguments --- mdf_forge/forge.py | 86 ++++++++++++++++++++++++++------------------- tests/test_forge.py | 16 +++++---- 2 files changed, 59 insertions(+), 43 deletions(-) diff --git a/mdf_forge/forge.py b/mdf_forge/forge.py index 60efbf6..4cbb3ad 100644 --- a/mdf_forge/forge.py +++ b/mdf_forge/forge.py @@ -437,45 +437,57 @@ def match_titles(self, titles): return self - def match_years(self, years, match_all=True): + def match_years(self, years=[], min = None, max = None, include=True): """Add years and limits to the query. Arguments: - years (int, str or list of int or str): The years or range to match. - match_all (bool) of 1) list of years: If True, will add with AND. If False, will use OR. - 2) min, max range: If True, the start and stop values will be included in the search. If False, - neither of them will be included. - Default True. + years (int, str or list of int or str): The years to match. Default []. + min (int of str): The lower range of years to match.. + max (int of str): The upper range of years to match. + include (bool): If True, will add min and max years to the range. If False, they will be excluded. + Default True. + limit (int): The maximum number of results to return. + The max for this argument is the SEARCH_LIMIT imposed by Globus Search. + info (bool): If False, search will return a list of the results. + If True, search will return a tuple containing the results list, + and other information about the query. Default False. Returns: self (Forge): For chaining. """ - if not years: + if not years and not min and not max: + print_("Year is not a valid input.") return self - if not isinstance(years, list): - years = [years] - - year_start = "*"; year_stop = "*" - years_new = [] - for year in years: - year = str(year) - if year.isdigit() and len(year) == 4: - years_new.append(str(year)) - elif "min=" in year and year[4:8].isdigit() and len(year) == 8: - year_start = year[4:8] - elif "max=" in year and year[4:8].isdigit() and len(year) == 8: - year_stop = year[4:8] - else: - print("Year is not a valid input; use 'yyyy', 'min=yyyy', or 'max=yyyy'.") - return self - if year_start == "*" and year_stop == "*": + if years: + if not isinstance(years, list): + years = [years] + years_new = [] + for year in years: + year = str(year) + if year.isdigit() and len(year) == 4: + years_new.append(str(year)) + else: + print_("Year is not a valid input; use 'yyyy'.") + return self + self.match_field(field="mdf.year", value=years_new[0], required=True, new_group=True) for year in years_new[1:]: - self.match_field(field="mdf.year", value=year, required=match_all, - new_group=False) + self.match_field(field="mdf.year", value=year, required=False, new_group=False) else: - self.match_range(field="mdf.year", start=year_start, stop=year_stop, inclusive=match_all, required=True, - new_group=False) + year_start = "*"; year_stop = "*" + if min: + year_start = str(min) + if not year_start.isdigit() or not len(year_start) == 4: + print_("Year is not a valid input; use min='yyyy'.") + return self + if max: + year_stop = str(max) + if not year_stop.isdigit() or not len(year_stop) == 4: + print_("Year is not a valid input; use max='yyyy'.") + return self + + self.match_range(field="mdf.year", start=year_start, stop=year_stop, inclusive=include, required=True, + new_group=False) return self @@ -576,27 +588,27 @@ def search_by_tags(self, tags, limit=None, match_all=True, info=False): return self.match_tags(tags, match_all=match_all).search(limit=limit, info=info) - def search_by_years(self, years, limit=None, match_all=True, info=False): + def search_by_years(self, years=[], min=None, max=None, include=True, limit=None, info=False): """Execute a search for the given year or years. search_by_years([x]) is equivalent to match_years([x]).search() Arguments: - years (list of str): The years to match. Default []. + years (int, str or list of int or str): The years to match. + min (int of str): The lower range of years to match.. + max (int of str): The upper range of years to match. + include (bool): If True, will add min and max years to the range. If False, they will be excluded. + Default True. limit (int): The maximum number of results to return. The max for this argument is the SEARCH_LIMIT imposed by Globus Search. - match_all (bool): If True, will add elements with AND. - If False, will use OR. - Default True. info (bool): If False, search will return a list of the results. - If True, search will return a tuple containing the results list, - and other information about the query. - Default False. + If True, search will return a tuple containing the results list, + and other information about the query. Default False. Returns: list (if info=False): The results. tuple (if info=True): The results, and a dictionary of query information. """ - return self.match_years(years, match_all=match_all).search(limit=limit, info=info) + return self.match_years(years, min, max, include=include).search(limit=limit, info=info) def aggregate_source(self, sources): diff --git a/tests/test_forge.py b/tests/test_forge.py index d44d7f7..e8bfa05 100644 --- a/tests/test_forge.py +++ b/tests/test_forge.py @@ -548,8 +548,8 @@ def test_forge_match_tags(): def test_forge_match_years(capfd): # One year of data/results f1 = forge.Forge() - years1 = "2015" - res1 = f1.match_years(years1).search() + years1 = ["2015"] + res1 = f1.match_years(years1).search(limit=10) assert res1 != [] assert check_field(res1, "mdf.year", 2015) == 0 @@ -557,13 +557,13 @@ def test_forge_match_years(capfd): f2 = forge.Forge() years2 = [2015,"2011"] # match_all=False (2011 OR 2015) - res2 = f2.match_years(years2, match_all=False).search() + res2 = f2.match_years(years2, include=False).search() assert check_field(res2, "mdf.year", 2011) == 2 # Wrong input f3 = forge.Forge() years3 = ["20x5"] - res3 = f3.match_years(years3, match_all=False).search() + res3 = f3.match_years(years3, include=False).search() out, err = capfd.readouterr() msg_err = "Year is not a valid input" in out assert msg_err == True @@ -663,13 +663,17 @@ def test_forge_search_by_tags(): def test_forge_search_by_years(): f1 = forge.Forge() - years1 = ["min=2015","max=2016"] - res1 = f1.search_by_years(years1) + res1 = f1.search_by_years(min=2015,max=2016) r1 = check_field(res1, "mdf.year", 2015) == 2 r2 = check_field(res1, "mdf.year", 2016) == 2 r3 = check_field(res1, "mdf.year", 2017) == -1 assert all(r==True for r in [r1, r2, r3]) + f2 = forge.Forge() + res2 = f2.search_by_years(max=1960,include=False) + assert check_field(res2, "mdf.year", 1959) == 2 + + def test_forge_aggregate_source(): # Test limit From 8878826e8e424cca46bec94f97123ba8322c065c Mon Sep 17 00:00:00 2001 From: ncsa-mo Date: Fri, 17 Nov 2017 13:13:32 -0600 Subject: [PATCH 07/22] change cast to integers, and default for the years argument to be None --- mdf_forge/forge.py | 63 ++++++++++++++++++++++++--------------------- tests/test_forge.py | 16 ++++++------ 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/mdf_forge/forge.py b/mdf_forge/forge.py index 4cbb3ad..b9ae8d1 100644 --- a/mdf_forge/forge.py +++ b/mdf_forge/forge.py @@ -437,14 +437,14 @@ def match_titles(self, titles): return self - def match_years(self, years=[], min = None, max = None, include=True): + def match_years(self, years=None, min = None, max = None, inclusive=True): """Add years and limits to the query. Arguments: - years (int, str or list of int or str): The years to match. Default []. - min (int of str): The lower range of years to match.. - max (int of str): The upper range of years to match. - include (bool): If True, will add min and max years to the range. If False, they will be excluded. + years (int or string, or list of int or strings): The years to match. + min (int or string): The lower range of years to match. + max (int or string): The upper range of years to match. + inclusive (bool): If True, will add min and max years to the range. If False, they will be excluded. Default True. limit (int): The maximum number of results to return. The max for this argument is the SEARCH_LIMIT imposed by Globus Search. @@ -454,39 +454,44 @@ def match_years(self, years=[], min = None, max = None, include=True): Returns: self (Forge): For chaining. """ - if not years and not min and not max: + if years is None and min is None and max is None: print_("Year is not a valid input.") return self - if years: + if years is not None: + if not years: + return self if not isinstance(years, list): years = [years] years_new = [] for year in years: - year = str(year) - if year.isdigit() and len(year) == 4: - years_new.append(str(year)) - else: - print_("Year is not a valid input; use 'yyyy'.") + try: + year = int(year) + print(year) + years_new.append(year) + except: + print_("Year is not a valid input.") return self - self.match_field(field="mdf.year", value=years_new[0], required=True, new_group=True) + self.match_field(field="mdf.year", value=str(years_new[0]), required=True, new_group=True) for year in years_new[1:]: - self.match_field(field="mdf.year", value=year, required=False, new_group=False) + self.match_field(field="mdf.year", value=str(year), required=False, new_group=False) else: year_start = "*"; year_stop = "*" - if min: - year_start = str(min) - if not year_start.isdigit() or not len(year_start) == 4: - print_("Year is not a valid input; use min='yyyy'.") + if min is not None: + try: + year_start = int(min) + except: + print_("Year is not a valid input.") return self - if max: - year_stop = str(max) - if not year_stop.isdigit() or not len(year_stop) == 4: - print_("Year is not a valid input; use max='yyyy'.") + if max is not None: + try: + year_stop = int(max) + except: + print_("Year is not a valid input.") return self - self.match_range(field="mdf.year", start=year_start, stop=year_stop, inclusive=include, required=True, + self.match_range(field="mdf.year", start=str(year_start), stop=str(year_stop), inclusive=inclusive, required=True, new_group=False) return self @@ -588,15 +593,15 @@ def search_by_tags(self, tags, limit=None, match_all=True, info=False): return self.match_tags(tags, match_all=match_all).search(limit=limit, info=info) - def search_by_years(self, years=[], min=None, max=None, include=True, limit=None, info=False): + def search_by_years(self, years=None, min=None, max=None, inclusive=True, limit=None, info=False): """Execute a search for the given year or years. search_by_years([x]) is equivalent to match_years([x]).search() Arguments: - years (int, str or list of int or str): The years to match. - min (int of str): The lower range of years to match.. - max (int of str): The upper range of years to match. - include (bool): If True, will add min and max years to the range. If False, they will be excluded. + years (int or string, or list of int or strings): The years to match. + min (int or string): The lower range of years to match. + max (int or string): The upper range of years to match. + inclusive (bool): If True, will add min and max years to the range. If False, they will be excluded. Default True. limit (int): The maximum number of results to return. The max for this argument is the SEARCH_LIMIT imposed by Globus Search. @@ -608,7 +613,7 @@ def search_by_years(self, years=[], min=None, max=None, include=True, limit=None list (if info=False): The results. tuple (if info=True): The results, and a dictionary of query information. """ - return self.match_years(years, min, max, include=include).search(limit=limit, info=info) + return self.match_years(years, min, max, inclusive=inclusive).search(limit=limit, info=info) def aggregate_source(self, sources): diff --git a/tests/test_forge.py b/tests/test_forge.py index e8bfa05..869b7ef 100644 --- a/tests/test_forge.py +++ b/tests/test_forge.py @@ -544,7 +544,7 @@ def test_forge_match_tags(): f4 = forge.Forge() assert f4.match_tags("") == f4 - +@pytest.mark.match_years def test_forge_match_years(capfd): # One year of data/results f1 = forge.Forge() @@ -557,13 +557,13 @@ def test_forge_match_years(capfd): f2 = forge.Forge() years2 = [2015,"2011"] # match_all=False (2011 OR 2015) - res2 = f2.match_years(years2, include=False).search() + res2 = f2.match_years(years2, inclusive=False).search() assert check_field(res2, "mdf.year", 2011) == 2 # Wrong input f3 = forge.Forge() years3 = ["20x5"] - res3 = f3.match_years(years3, include=False).search() + res3 = f3.match_years(years3, inclusive=False).search() out, err = capfd.readouterr() msg_err = "Year is not a valid input" in out assert msg_err == True @@ -660,7 +660,7 @@ def test_forge_search_by_tags(): assert len(res3) > len(res2) assert all([r in res3 for r in res2]) - +@pytest.mark.search_by_years def test_forge_search_by_years(): f1 = forge.Forge() res1 = f1.search_by_years(min=2015,max=2016) @@ -668,12 +668,12 @@ def test_forge_search_by_years(): r2 = check_field(res1, "mdf.year", 2016) == 2 r3 = check_field(res1, "mdf.year", 2017) == -1 assert all(r==True for r in [r1, r2, r3]) - f2 = forge.Forge() - res2 = f2.search_by_years(max=1960,include=False) + res2 = f2.search_by_years(max=1960,inclusive=False) assert check_field(res2, "mdf.year", 1959) == 2 - - + f3 = forge.Forge() + res3 = f3.search_by_years(min=-0, max="-10",inclusive=True) + assert check_field(res3, "mdf.year", 2010) == -1 def test_forge_aggregate_source(): # Test limit From 6b6c6766dd5f22cde506523d200641c0064831d8 Mon Sep 17 00:00:00 2001 From: jgaff Date: Tue, 28 Nov 2017 16:17:29 -0600 Subject: [PATCH 08/22] Flake8 integration Automatically run Flake8 on Travis. Also minor PEP8 compliance fixes. --- .travis.yml | 3 +- mdf_forge/forge.py | 143 +++++++++++++++----------------------------- setup.cfg | 5 +- setup.py | 7 ++- tests/test_forge.py | 48 ++++++--------- 5 files changed, 78 insertions(+), 128 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1f4aedb..59eb861 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,10 +7,11 @@ python: - '3.6' install: - pip install -e . -- pip install pytest pytest-cov coveralls +- pip install pytest pytest-cov coveralls flake8 env: - TEST_ENV=travis script: +- flake8 . - travis_wait 50 py.test before_install: - openssl aes-256-cbc -K $encrypted_4b438100ad6f_key -iv $encrypted_4b438100ad6f_iv diff --git a/mdf_forge/forge.py b/mdf_forge/forge.py index b35deb7..9e20f5e 100644 --- a/mdf_forge/forge.py +++ b/mdf_forge/forge.py @@ -55,25 +55,22 @@ def __init__(self, index=__default_index, local_ep=None, **kwargs): self.__query = Query(self.__search_client) - @property def search_client(self): return self.__search_client - @property def transfer_client(self): return self.__transfer_client - @property def mdf_authorizer(self): return self.__mdf_authorizer -################################################# -## Core functions -################################################# +# *********************************************** +# * Core functions +# *********************************************** def match_field(self, field, value, required=True, new_group=False): """Add a field:value term to the query. @@ -101,7 +98,6 @@ def match_field(self, field, value, required=True, new_group=False): self.__query.field(field, value) return self - def exclude_field(self, field, value, new_group=False): """Exclude a field:value term from the query. Matches will NOT have field == value. @@ -125,7 +121,6 @@ def exclude_field(self, field, value, new_group=False): self.__query.negate().field(field, value) return self - def search(self, q=None, advanced=False, limit=SEARCH_LIMIT, info=False, reset_query=True): """Execute a search and return the results. @@ -136,7 +131,7 @@ def search(self, q=None, advanced=False, limit=SEARCH_LIMIT, info=False, advanced (bool): If True, will submit query in "advanced" mode to enable field matches. If False, only basic fulltext term matches will be supported. Default False. - This value will change to True automatically + This value will change to True automatically if the query is built with helpers. limit (int): The maximum number of results to return. The max for this argument is the SEARCH_LIMIT imposed by Globus Search. @@ -157,7 +152,6 @@ def search(self, q=None, advanced=False, limit=SEARCH_LIMIT, info=False, self.reset_query() return res - def aggregate(self, q=None, scroll_size=SEARCH_LIMIT, reset_query=True): """Perform an advanced query, and return all matching results. Will automatically preform multiple queries in order to retrieve all results. @@ -180,7 +174,6 @@ def aggregate(self, q=None, scroll_size=SEARCH_LIMIT, reset_query=True): self.reset_query() return res - def show_fields(self, block=None): """Retrieve and return the mapping for the given metadata block. @@ -206,7 +199,6 @@ def show_fields(self, block=None): block_map[key] = value return block_map - def current_query(self): """Return the current query string. @@ -215,10 +207,9 @@ def current_query(self): """ return self.__query.clean_query() - def reset_query(self): """Destroy the current query and create a fresh one. - + Returns: None: Does not return self because this method should not be chained. """ @@ -226,9 +217,9 @@ def reset_query(self): self.__query = Query(self.__search_client) -################################################# -## Expanded functions -################################################# +# *********************************************** +# * Expanded functions +# *********************************************** def match_range(self, field, start, stop, inclusive=True, required=True, new_group=False): """Add a field:[some range] term to the query. @@ -257,7 +248,6 @@ def match_range(self, field, start, stop, inclusive=True, required=True, new_gro self.match_field(field, value, required=required, new_group=new_group) return self - def exclude_range(self, field, start, stop, inclusive=True, new_group=False): """Exclude a field:[some range] term to the query. Matches will have field != values in range. @@ -285,13 +275,13 @@ def exclude_range(self, field, start, stop, inclusive=True, new_group=False): return self -################################################# -## Helper functions -################################################# +# *********************************************** +# * Helper functions +# *********************************************** def exclusive_match(self, field, value): """Match exactly the given value, with no other data in the field. - + Arguments: field (str): The field to check for the value. The field must be namespaced according to Elasticsearch rules using the dot syntax. @@ -307,11 +297,11 @@ def exclusive_match(self, field, value): # Hacky way to get ES to do exclusive search # Essentially have a big range search that matches NOT anything # Except for the actual values - # Example: [foo, bar, baz] => + # Example: [foo, bar, baz] => # (NOT {* TO foo} AND [foo TO foo] AND NOT {foo to bar} AND [bar TO bar] # AND NOT {bar TO baz} AND [baz TO baz] AND NOT {baz TO *}) # Except it must be sorted to not overlap - + # Start with removing everything before first value self.exclude_range(field, "*", value[0], inclusive=False, new_group=True) # Select first value @@ -325,13 +315,12 @@ def exclusive_match(self, field, value): # Done return self - def match_sources(self, sources): """Add sources to match to the query. Arguments: sources (str or list of str): The sources to match. - + Returns: self (Forge): For chaining. """ @@ -347,7 +336,6 @@ def match_sources(self, sources): self.match_field(field="mdf.source_name", value=src, required=False, new_group=False) return self - def match_ids(self, mdf_ids): """Match all the IDs in the given mdf_id list. @@ -369,14 +357,13 @@ def match_ids(self, mdf_ids): self.match_field(field="mdf.mdf_id", value=mid, required=False, new_group=False) return self - def match_elements(self, elements, match_all=True): """Add elemental abbreviations to the query. Arguments: elements (str or list of str): The elements to match. match_all (bool): If True, will add with AND. If False, will use OR. Default True. - + Returns: self (Forge): For chaining. """ @@ -393,7 +380,6 @@ def match_elements(self, elements, match_all=True): new_group=False) return self - def match_tags(self, tags, match_all=True): """Add tags to the query. @@ -416,7 +402,6 @@ def match_tags(self, tags, match_all=True): new_group=False) return self - def match_titles(self, titles): """Add titles to the query. @@ -436,7 +421,6 @@ def match_titles(self, titles): self.match_field(field="mdf.title", value=title, required=False, new_group=False) return self - def match_resource_types(self, types): """Match the given resource types. @@ -457,11 +441,11 @@ def match_resource_types(self, types): for rt in types[1:]: self.match_field(field="mdf.resource_type", value=rt, required=False, new_group=False) return self - -################################################# -## Premade searches -################################################# + +# *********************************************** +# * Premade searches +# *********************************************** def search_by_elements(self, elements, sources=[], limit=None, match_all=True, info=False): """Execute a search for the given elements in the given sources. @@ -474,7 +458,7 @@ def search_by_elements(self, elements, sources=[], limit=None, match_all=True, i sources (list of str): The sources to match. Default []. limit (int): The maximum number of results to return. The max for this argument is the SEARCH_LIMIT imposed by Globus Search. - match_all (bool): If True, will add elements with AND. + match_all (bool): If True, will add elements with AND. If False, will use OR. Default True. info (bool): If False, search will return a list of the results. @@ -490,7 +474,6 @@ def search_by_elements(self, elements, sources=[], limit=None, match_all=True, i .match_sources(sources) .search(limit=limit, info=info)) - def search_by_titles(self, titles, limit=None, info=False): """Execute a search for the given titles. search_by_titles([x]) is equivalent to match_titles([x]).search() @@ -510,7 +493,6 @@ def search_by_titles(self, titles, limit=None, info=False): """ return self.match_titles(titles).search(limit=limit, info=info) - def search_by_tags(self, tags, limit=None, match_all=True, info=False): """Execute a search for the given tag. search_by_tags([x]) is equivalent to match_tags([x]).search() @@ -533,7 +515,6 @@ def search_by_tags(self, tags, limit=None, match_all=True, info=False): """ return self.match_tags(tags, match_all=match_all).search(limit=limit, info=info) - def aggregate_source(self, sources): """Aggregate all records from a given source. There is no limit to the number of results returned. @@ -541,13 +522,12 @@ def aggregate_source(self, sources): Arguments: sources (str or list of str): The source to aggregate. - + Returns: list of dict: All of the records from the source. """ return self.match_sources(sources).aggregate() - def fetch_datasets_from_results(self, entries=None, query=None, reset_query=True): """Retrieve the dataset entries for given records. Note that this method may use the current query. @@ -593,9 +573,9 @@ def fetch_datasets_from_results(self, entries=None, query=None, reset_query=True return self.match_ids(list(ds_ids)).search() -################################################# -## Data retrieval functions -################################################# +# *********************************************** +# * Data retrieval functions +# *********************************************** def http_download(self, results, dest=".", preserve_dir=False, verbose=True): """Download data files from the provided results using HTTPS. @@ -633,7 +613,7 @@ def http_download(self, results, dest=".", preserve_dir=False, verbose=True): + str(HTTP_NUM_LIMIT) + " entries.") } - for res in tqdm(results, desc="Fetching files", disable= not verbose): + for res in tqdm(results, desc="Fetching files", disable=(not verbose)): for key in res["mdf"]["links"].keys(): dl = res["mdf"]["links"][key] host = dl.get("http_host", None) if type(dl) is dict else None @@ -642,7 +622,7 @@ def http_download(self, results, dest=".", preserve_dir=False, verbose=True): # local_path should be either dest + whole path or dest + filename if preserve_dir: local_path = os.path.normpath(dest + "/" + dl["path"]) - else: + else: local_path = os.path.normpath(dest + "/" + os.path.basename(dl["path"])) # Make dirs for storing the file if they don't exist # preserve_dir doesn't matter; local_path has accounted for it already @@ -683,7 +663,7 @@ def http_download(self, results, dest=".", preserve_dir=False, verbose=True): self.response = requests.get(host+remote_path, headers=headers) # Handle other errors by passing the buck to the user if response.status_code != 200: - print_("Error ", response.status_code, + print_("Error ", response.status_code, " when attempting to access '", host+remote_path, "'", sep="") else: @@ -691,7 +671,6 @@ def http_download(self, results, dest=".", preserve_dir=False, verbose=True): with open(local_path, 'wb') as output: output.write(response.content) - def globus_download(self, results, dest=".", dest_ep=None, preserve_dir=False, wait_for_completion=True, verbose=True): """Download data files from the provided results using Globus Transfer. @@ -703,7 +682,7 @@ def globus_download(self, results, dest=".", dest_ep=None, preserve_dir=False, dest (str): The destination path for the data files on the local machine. Default current directory. preserve_dir (bool): If True, the directory structure for the data files will be - recreated at the destination. The path to the new files + recreated at the destination. The path to the new files will be relative to the `dest` path If False, only the data files themselves will be saved. Default False. @@ -715,7 +694,7 @@ def globus_download(self, results, dest=".", dest_ep=None, preserve_dir=False, Default True. Returns: - list of str: task IDs of the Gloubs transfers + list of str: task IDs of the Globus transfers """ dest = os.path.abspath(dest) # If results have info attached, remove it @@ -729,10 +708,9 @@ def globus_download(self, results, dest=".", dest_ep=None, preserve_dir=False, # Assemble the transfer data tasks = {} filenames = set() - for res in tqdm(results, desc="Processing records", disable= not verbose): + for res in tqdm(results, desc="Processing records", disable=(not verbose)): found = False - for key in tqdm(res["mdf"]["links"].keys(), desc="Fetching files", disable=True): - + for key in res["mdf"]["links"].keys(): # Get the location of the data dl = res["mdf"]["links"][key] host = dl.get("globus_endpoint", None) if type(dl) is dict else None @@ -744,12 +722,12 @@ def globus_download(self, results, dest=".", dest_ep=None, preserve_dir=False, remote_path = dl["path"] # local_path should be either dest + whole path or dest + filename if preserve_dir: - # remote_path is absolute, so os.path.join does not work - local_path = os.path.abspath(dest + remote_path) + # remote_path is absolute, so os.path.join does not work + local_path = os.path.abspath(dest + remote_path) else: local_path = os.path.abspath( - os.path.join(dest, - os.path.basename(remote_path))) + os.path.join(dest, + os.path.basename(remote_path))) # Make dirs for storing the file if they don't exist # preserve_dir doesn't matter; local_path has accounted for it already @@ -792,16 +770,16 @@ def globus_download(self, results, dest=".", dest_ep=None, preserve_dir=False, # if need be, send it early if host not in tasks.keys(): tasks[host] = globus_sdk.TransferData(self.__transfer_client, - host, dest_ep, verify_checksum=True) + host, dest_ep, + verify_checksum=True) tasks[host].add_item(remote_path, local_path) filenames.add(local_path) if not found: print_("Error on record: No globus_endpoint provided.\nRecord: ", + str(res)) - # Submit the jobs submissions = [] - for td in tqdm(tasks.values(), desc="Submitting transfers", disable= not verbose): + for td in tqdm(tasks.values(), desc="Submitting transfers", disable=(not verbose)): result = self.__transfer_client.submit_transfer(td) if result["code"] != "Accepted": raise globus_sdk.GlobusError("Error submitting transfer:", result["message"]) @@ -811,13 +789,10 @@ def globus_download(self, results, dest=".", dest_ep=None, preserve_dir=False, polling_interval=10): if verbose: print_("Transferring...") - for event in transfer_client.task_event_list(res["task_id"]): + for event in self.__transfer_client.task_event_list(res["task_id"]): if event["is_error"]: - transfer_client.cancel_task(res["task_id"]) - raise GlobusError("Error: " + event["description"]) - if config_data["timeout"] and intervals >= timeout_intervals: - transfer_client.cancel_task(res["task_id"]) - raise GlobusError("Transfer timed out.") + self.__transfer_client.cancel_task(res["task_id"]) + raise globus_sdk.GlobusError("Error: " + event["description"]) submissions.append(result["task_id"]) if verbose: @@ -825,7 +800,6 @@ def globus_download(self, results, dest=".", dest_ep=None, preserve_dir=False, print_("Task IDs:", "\n".join(submissions)) return submissions - def http_stream(self, results, verbose=True): """Yield data files from the provided results using HTTPS, through a generator. For more than HTTP_NUM_LIMIT (defined above) files, you should use globus_download(), @@ -881,7 +855,6 @@ def http_stream(self, results, verbose=True): else: yield response.text - def http_return(self, results, verbose=True): """Return data files from the provided results using HTTPS. For more than HTTP_NUM_LIMIT (defined above) files, you should use globus_download(), @@ -900,12 +873,11 @@ def http_return(self, results, verbose=True): return list(self.http_stream(results, verbose=verbose)) - class Query: """The Query class is meant for internal Forge use. Users should not instantiate a Query object directly, as Forge already manages a Query, but advanced users may do so at their own risk. - Using Query directly is an unsupported behavior + Using Query directly is an unsupported behavior and may have unexpected results or unlisted changes in the future. Queries may end up wrapped in parentheses, which has no direct effect on the search. @@ -920,7 +892,7 @@ def __init__(self, search_client, q=None, limit=None, advanced=False): search_client (SearchClient): The Globus Search client to use for searching. q (str): The query string to start with. Default nothing. limit (int): The maximum number of results to return. Default None. - advanced (bool): If True, will submit query in "advanced" mode ro enable field matches. + advanced (bool): If True, will submit query in "advanced" mode to enable field matches. If False, only basic fulltext term matches will be supported. Default False. """ @@ -932,7 +904,6 @@ def __init__(self, search_client, q=None, limit=None, advanced=False): # __init__(), term(), and field() can change this value to True self.initialized = not self.query == "(" - def __clean_query_string(self, q): """Clean up a query string. This method does not access self, so that a search will not change state. @@ -951,9 +922,8 @@ def __clean_query_string(self, q): q += ")" while q.count(")") > q.count("("): q = "(" + q - - return q.strip() + return q.strip() def clean_query(self): """Returns the current query, cleaned for user consumption, @@ -963,7 +933,6 @@ def clean_query(self): """ return self.__clean_query_string(self.query) - def term(self, term): """Add a term to the query. @@ -977,7 +946,6 @@ def term(self, term): self.initialized = True return self - def field(self, field, value): """Add a field:value term to the query. Matches will have field == value. @@ -986,7 +954,7 @@ def field(self, field, value): Arguments: field (str): The field to look in for the value. value (str): The value to match. - + Returns: self (Query): For chaining. """ @@ -998,7 +966,6 @@ def field(self, field, value): self.initialized = True return self - def operator(self, op, close_group=False): """Add operator between terms. There must be a term added before using this method. @@ -1026,7 +993,6 @@ def operator(self, op, close_group=False): self.query += op return self - def and_join(self, close_group=False): """Combine terms with AND. There must be a term added before using this method. @@ -1048,7 +1014,6 @@ def and_join(self, close_group=False): self.operator("AND", close_group=close_group) return self - def or_join(self, close_group=False): """Combine terms with OR. There must be a term added before using this method. @@ -1070,13 +1035,11 @@ def or_join(self, close_group=False): self.operator("OR", close_group=close_group) return self - def negate(self): """Negates the next term with NOT.""" self.operator("NOT") return self - def search(self, q=None, advanced=None, limit=SEARCH_LIMIT, info=False): """Execute a search and return the results. @@ -1126,7 +1089,6 @@ def search(self, q=None, advanced=None, limit=SEARCH_LIMIT, info=False): res[1]["query"] = qu return res - def aggregate(self, q=None, scroll_size=SEARCH_LIMIT): """Gather all results that match a specific query @@ -1147,7 +1109,7 @@ def aggregate(self, q=None, scroll_size=SEARCH_LIMIT): return [] q = self.__clean_query_string(q) - #TODO: Remove record restriction (all entries require scroll_id) + # TODO: Remove record restriction (all entries require scroll_id) q += " AND mdf.resource_type:record" # Get the total number of records @@ -1156,7 +1118,7 @@ def aggregate(self, q=None, scroll_size=SEARCH_LIMIT): # Scroll until all results are found output = [] - #TODO: scroll_pos = 0 (when all entries have scroll_id) + # TODO: scroll_pos = 0 (when all entries have scroll_id) scroll_pos = 1 with tqdm(total=total) as pbar: while len(output) < total: @@ -1168,13 +1130,6 @@ def aggregate(self, q=None, scroll_size=SEARCH_LIMIT): # scroll width is much smaller than that maximum scroll_width = scroll_size while True: - struct_query = { - "q": "(" + q + ') AND mdf.scroll_id:>=%d AND mdf.scroll_id:<%d' % ( - scroll_pos, scroll_pos+scroll_width), - "advanced": True, - "limit": SEARCH_LIMIT, - "offset": 0 - } query = "(" + q + ') AND (mdf.scroll_id:>=%d AND mdf.scroll_id:<%d)' % ( scroll_pos, scroll_pos+scroll_width) results, info = self.search(query, advanced=True, info=True) @@ -1196,7 +1151,6 @@ def aggregate(self, q=None, scroll_size=SEARCH_LIMIT): return output - def mapping(self): """Fetch the mapping for the current index. @@ -1204,4 +1158,3 @@ def mapping(self): dict: The full mapping for the index. """ return self.__search_client.mapping()["mappings"] - diff --git a/setup.cfg b/setup.cfg index 87d6b83..585ec41 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,10 @@ [bdist_wheel] # Forge is compatible with Python 2 and 3 -universal=1 +universal = 1 [tool:pytest] addopts = --ignore=setup.py --cov=mdf_forge +[flake8] +exclude = .git,*.egg* +max-line-length = 100 diff --git a/setup.py b/setup.py index e27c92e..5a56f47 100644 --- a/setup.py +++ b/setup.py @@ -5,9 +5,12 @@ version='0.5.0', packages=['mdf_forge'], description='Materials Data Facility python package', - long_description="Forge is the Materials Data Facility Python package to interface and leverage the MDF Data Discovery service. Forge allows users to perform simple queries and facilitiates moving and synthesizing results.", + long_description=("Forge is the Materials Data Facility Python package" + " to interface and leverage the MDF Data Discovery service. " + "Forge allows users to perform simple queries and " + "facilitiates moving and synthesizing results."), install_requires=[ - "mdf-toolbox>=0.1.0", + "mdf-toolbox>=0.1.1", "globus-sdk>=1.2.1", "requests>=2.18.4", "tqdm>=4.19.4", diff --git a/tests/test_forge.py b/tests/test_forge.py index 3216607..893d4e7 100644 --- a/tests/test_forge.py +++ b/tests/test_forge.py @@ -1,5 +1,4 @@ import os -import time import types import pytest import globus_sdk @@ -8,26 +7,23 @@ # Manually logging in for Query testing -query_search_client = toolbox.login(credentials={"app_name": "MDF_Forge", - "services": ["search"], "index": "mdf"})["search"] +query_search_client = toolbox.login(credentials={"app_name": "MDF_Forge", + "services": ["search"], + "index": "mdf"})["search"] -############################ -# Query tests -############################ - def test_query_init(): q1 = forge.Query(query_search_client) assert q1.query == "(" - assert q1.limit == None - assert q1.advanced == False - assert q1.initialized == False + assert q1.limit is None + assert q1.advanced is False + assert q1.initialized is False q2 = forge.Query(query_search_client, q="mdf.source_name:oqmd", limit=5, advanced=True) assert q2.query == "mdf.source_name:oqmd" assert q2.limit == 5 - assert q2.advanced == True - assert q2.initialized == True + assert q2.advanced is True + assert q2.initialized is True def test_query_term(): @@ -35,7 +31,7 @@ def test_query_term(): # Single match test assert isinstance(q.term("term1"), forge.Query) assert q.query == "(term1" - assert q.initialized == True + assert q.initialized is True # Multi-match test q.and_join().term("term2") assert q.query == "(term1 AND term2" @@ -211,11 +207,6 @@ def test_query_cleaning(): assert q10.clean_query() == "term OR term2" - -############################ -# Forge tests -############################ - # Test properties def test_forge_properties(): f1 = forge.Forge() @@ -293,7 +284,7 @@ def check_field(res, field, value): if field not in supported_fields: raise ValueError("Implement or re-spell " + field - + "because check_field only works on " + + "because check_field only works on " + str(supported_fields)) # If no results, set matches to false all_match = (len(res) > 0) @@ -332,16 +323,16 @@ def check_field(res, field, value): some_match = True if only_match: - #print("Exclusive match") + # Exclusive match return 0 elif all_match: - #print("Inclusive match") + # Inclusive match return 1 elif some_match: - #print("Partial match") + # Partial match return 2 else: - #print("No match") + # No match return -1 @@ -553,7 +544,7 @@ def test_forge_match_resource_types(): f2.match_resource_types(["collection", "dataset"]) res2 = f2.search() assert check_field(res2, "mdf.resource_type", "record") == -1 - #TODO: Re-enable this assert after we get collections in MDF + # TODO: Re-enable this assert after we get collections in MDF # assert check_field(res2, "mdf.resource_type", "dataset") == 2 # Test zero types f3 = forge.Forge() @@ -751,7 +742,7 @@ def test_forge_http_download(capsys): os.remove(os.path.join(dest_path, "test_multifetch.txt")) # Too many files - assert f.http_download(list(range(10001)))["success"] == False + assert f.http_download(list(range(10001)))["success"] is False # "Missing" files f.http_download(example_result_missing) @@ -765,7 +756,7 @@ def test_forge_http_download(capsys): def test_forge_globus_download(): f = forge.Forge() # Simple case - res1 = f.globus_download(example_result1) + f.globus_download(example_result1) assert os.path.exists("./test_fetch.txt") os.remove("./test_fetch.txt") # With dest and preserve_dir @@ -789,13 +780,13 @@ def test_forge_http_stream(capsys): assert isinstance(res1, types.GeneratorType) assert next(res1) == "This is a test document for Forge testing. Please do not remove.\n" # With multiple files - res2 = f1.http_stream((example_result2, {"info":{}})) + res2 = f1.http_stream((example_result2, {"info": {}})) assert isinstance(res2, types.GeneratorType) assert next(res2) == "This is a test document for Forge testing. Please do not remove.\n" assert next(res2) == "This is a second test document for Forge testing. Please do not remove.\n" # Too many results res3 = f1.http_stream(list(range(10001))) - assert next(res3)["success"] == False + assert next(res3)["success"] is False out, err = capsys.readouterr() assert ("Too many results supplied. Use globus_download() for " "fetching more than 2000 entries.") in out @@ -839,4 +830,3 @@ def test_forge_show_fields(): assert "mdf" in res1.keys() res2 = f1.show_fields("mdf") assert "mdf.mdf_id" in res2.keys() - From f7d08bb4b504d14b1072ef0f0b71c8529021cf3e Mon Sep 17 00:00:00 2001 From: ncsa-mo Date: Tue, 5 Dec 2017 10:36:30 -0600 Subject: [PATCH 09/22] removed --- tests/test_forge.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_forge.py b/tests/test_forge.py index db91087..6b18098 100644 --- a/tests/test_forge.py +++ b/tests/test_forge.py @@ -535,7 +535,7 @@ def test_forge_match_tags(): f4 = forge.Forge() assert f4.match_tags("") == f4 -@pytest.mark.match_years + def test_forge_match_years(capfd): # One year of data/results f1 = forge.Forge() @@ -651,7 +651,7 @@ def test_forge_search_by_tags(): assert len(res3) > len(res2) assert all([r in res3 for r in res2]) -@pytest.mark.search_by_years + def test_forge_search_by_years(): f1 = forge.Forge() res1 = f1.search_by_years(min=2015,max=2016) From ba651d6e30d0ce83c2e0c44eb8f6a12074f8dddf Mon Sep 17 00:00:00 2001 From: ncsa-mo Date: Tue, 5 Dec 2017 10:50:56 -0600 Subject: [PATCH 10/22] merge conflict in cfg --- setup.cfg | 4 ---- 1 file changed, 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index 45fa0a8..585ec41 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,12 +3,8 @@ universal = 1 [tool:pytest] -<<<<<<< HEAD -addopts = --ignore=setup.py --cov=mdf_forge -======= addopts = --ignore=setup.py --cov=mdf_forge [flake8] exclude = .git,*.egg* max-line-length = 100 ->>>>>>> origin/forge-dev From 6ee1df4329857eacfcb007399856b76a1e81ea1b Mon Sep 17 00:00:00 2001 From: jgaff Date: Tue, 5 Dec 2017 16:00:12 -0600 Subject: [PATCH 11/22] Refreshing credentials, faster PEP8 failure Refresh Forge credentials to be safe. Do not run pytest tests if flake8 fails, to more easily deal with PEP8 noncompliance failures (which are fast, as opposed to the pytest tests). --- .travis.yml | 5 ++--- travis.tar.enc | Bin 3088 -> 10256 bytes 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 59eb861..881cdc0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,10 +11,9 @@ install: env: - TEST_ENV=travis script: -- flake8 . -- travis_wait 50 py.test +- flake8 . && travis_wait 50 py.test before_install: -- openssl aes-256-cbc -K $encrypted_4b438100ad6f_key -iv $encrypted_4b438100ad6f_iv +- openssl aes-256-cbc -K $encrypted_39a50b90a369_key -iv $encrypted_39a50b90a369_iv -in travis.tar.enc -out travis.tar -d - tar xvf travis.tar - mkdir -p ~/mdf/credentials/ diff --git a/travis.tar.enc b/travis.tar.enc index 9c1007b70e0af325f461810e63d0dda02c257610..2b0d613fcaf4211ebf126f9906a93ff9b62ad568 100644 GIT binary patch literal 10256 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 literal 3088 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 From 82cd808eb8cd699c4c784fe73fdbe1e1d39a717b Mon Sep 17 00:00:00 2001 From: ncsa-mo Date: Wed, 6 Dec 2017 10:08:36 -0600 Subject: [PATCH 12/22] formatting and pytest markers --- tests/test_forge.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_forge.py b/tests/test_forge.py index 6b18098..7da2912 100644 --- a/tests/test_forge.py +++ b/tests/test_forge.py @@ -535,7 +535,7 @@ def test_forge_match_tags(): f4 = forge.Forge() assert f4.match_tags("") == f4 - +@pytest.mark.match_years def test_forge_match_years(capfd): # One year of data/results f1 = forge.Forge() @@ -651,7 +651,7 @@ def test_forge_search_by_tags(): assert len(res3) > len(res2) assert all([r in res3 for r in res2]) - +@pytest.mark.search_by_years def test_forge_search_by_years(): f1 = forge.Forge() res1 = f1.search_by_years(min=2015,max=2016) @@ -666,6 +666,7 @@ def test_forge_search_by_years(): res3 = f3.search_by_years(min=-0, max="-10",inclusive=True) assert check_field(res3, "mdf.year", 2010) == -1 + def test_forge_aggregate_source(): # Test limit f1 = forge.Forge() From 0cf18d6974333a530359946322a695e3565512ee Mon Sep 17 00:00:00 2001 From: ncsa-mo Date: Wed, 6 Dec 2017 11:11:39 -0600 Subject: [PATCH 13/22] style fixed --- mdf_forge/forge.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/mdf_forge/forge.py b/mdf_forge/forge.py index 25379bd..1ea7999 100644 --- a/mdf_forge/forge.py +++ b/mdf_forge/forge.py @@ -421,15 +421,15 @@ def match_titles(self, titles): self.match_field(field="mdf.title", value=title, required=False, new_group=False) return self - - def match_years(self, years=None, min = None, max = None, inclusive=True): + def match_years(self, years=None, min=None, max=None, inclusive=True): """Add years and limits to the query. Arguments: years (int or string, or list of int or strings): The years to match. min (int or string): The lower range of years to match. max (int or string): The upper range of years to match. - inclusive (bool): If True, will add min and max years to the range. If False, they will be excluded. + inclusive (bool): If True, will add min and max years to the range. + If False, they will be excluded. Default True. limit (int): The maximum number of results to return. The max for this argument is the SEARCH_LIMIT imposed by Globus Search. @@ -454,33 +454,34 @@ def match_years(self, years=None, min = None, max = None, inclusive=True): year = int(year) print(year) years_new.append(year) - except: + except ValueError: print_("Year is not a valid input.") return self - self.match_field(field="mdf.year", value=str(years_new[0]), required=True, new_group=True) + self.match_field(field="mdf.year", value=str(years_new[0]), required=True, + new_group=True) for year in years_new[1:]: self.match_field(field="mdf.year", value=str(year), required=False, new_group=False) else: - year_start = "*"; year_stop = "*" + year_start = "*" + year_stop = "*" if min is not None: try: year_start = int(min) - except: + except ValueError: print_("Year is not a valid input.") return self if max is not None: try: year_stop = int(max) - except: + except ValueError: print_("Year is not a valid input.") return self - self.match_range(field="mdf.year", start=str(year_start), stop=str(year_stop), inclusive=inclusive, required=True, - new_group=False) + self.match_range(field="mdf.year", start=str(year_start), stop=str(year_stop), + inclusive=inclusive, required=True, new_group=False) return self - def match_resource_types(self, types): """Match the given resource types. @@ -575,8 +576,8 @@ def search_by_tags(self, tags, limit=None, match_all=True, info=False): """ return self.match_tags(tags, match_all=match_all).search(limit=limit, info=info) - - def search_by_years(self, years=None, min=None, max=None, inclusive=True, limit=None, info=False): + def search_by_years(self, years=None, min=None, max=None, inclusive=True, limit=None, + info=False): """Execute a search for the given year or years. search_by_years([x]) is equivalent to match_years([x]).search() @@ -584,7 +585,8 @@ def search_by_years(self, years=None, min=None, max=None, inclusive=True, limit= years (int or string, or list of int or strings): The years to match. min (int or string): The lower range of years to match. max (int or string): The upper range of years to match. - inclusive (bool): If True, will add min and max years to the range. If False, they will be excluded. + inclusive (bool): If True, will add min and max years to the range. + If False, they will be excluded. Default True. limit (int): The maximum number of results to return. The max for this argument is the SEARCH_LIMIT imposed by Globus Search. @@ -598,7 +600,6 @@ def search_by_years(self, years=None, min=None, max=None, inclusive=True, limit= """ return self.match_years(years, min, max, inclusive=inclusive).search(limit=limit, info=info) - def aggregate_source(self, sources): """Aggregate all records from a given source. There is no limit to the number of results returned. From 609e8bc98b105050166009096ba7e89323d7077c Mon Sep 17 00:00:00 2001 From: ncsa-mo Date: Wed, 6 Dec 2017 11:22:28 -0600 Subject: [PATCH 14/22] style fixed in tests --- tests/test_forge.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_forge.py b/tests/test_forge.py index 7da2912..8e1141a 100644 --- a/tests/test_forge.py +++ b/tests/test_forge.py @@ -535,7 +535,7 @@ def test_forge_match_tags(): f4 = forge.Forge() assert f4.match_tags("") == f4 -@pytest.mark.match_years + def test_forge_match_years(capfd): # One year of data/results f1 = forge.Forge() @@ -546,7 +546,7 @@ def test_forge_match_years(capfd): # Multiple years f2 = forge.Forge() - years2 = [2015,"2011"] + years2 = [2015, "2011"] # match_all=False (2011 OR 2015) res2 = f2.match_years(years2, inclusive=False).search() assert check_field(res2, "mdf.year", 2011) == 2 @@ -554,10 +554,10 @@ def test_forge_match_years(capfd): # Wrong input f3 = forge.Forge() years3 = ["20x5"] - res3 = f3.match_years(years3, inclusive=False).search() + res3 = f3.match_years(years3, inclusive=False).search() # noqa out, err = capfd.readouterr() msg_err = "Year is not a valid input" in out - assert msg_err == True + assert msg_err == True # noqa def test_forge_match_resource_types(): @@ -651,19 +651,19 @@ def test_forge_search_by_tags(): assert len(res3) > len(res2) assert all([r in res3 for r in res2]) -@pytest.mark.search_by_years + def test_forge_search_by_years(): f1 = forge.Forge() - res1 = f1.search_by_years(min=2015,max=2016) + res1 = f1.search_by_years(min=2015, max=2016) r1 = check_field(res1, "mdf.year", 2015) == 2 r2 = check_field(res1, "mdf.year", 2016) == 2 r3 = check_field(res1, "mdf.year", 2017) == -1 - assert all(r==True for r in [r1, r2, r3]) + assert all(r == True for r in [r1, r2, r3]) # noqa f2 = forge.Forge() - res2 = f2.search_by_years(max=1960,inclusive=False) + res2 = f2.search_by_years(max=1960, inclusive=False) assert check_field(res2, "mdf.year", 1959) == 2 f3 = forge.Forge() - res3 = f3.search_by_years(min=-0, max="-10",inclusive=True) + res3 = f3.search_by_years(min=-0, max="-10", inclusive=True) assert check_field(res3, "mdf.year", 2010) == -1 From 3995627deb124e000bece7546bb6476b6960d2ad Mon Sep 17 00:00:00 2001 From: ncsa-mo Date: Wed, 6 Dec 2017 11:47:24 -0600 Subject: [PATCH 15/22] change comparisons in assert, removed return variable --- tests/test_forge.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_forge.py b/tests/test_forge.py index 8e1141a..a81691f 100644 --- a/tests/test_forge.py +++ b/tests/test_forge.py @@ -554,10 +554,10 @@ def test_forge_match_years(capfd): # Wrong input f3 = forge.Forge() years3 = ["20x5"] - res3 = f3.match_years(years3, inclusive=False).search() # noqa + f3.match_years(years3, inclusive=False).search() out, err = capfd.readouterr() msg_err = "Year is not a valid input" in out - assert msg_err == True # noqa + assert msg_err is True def test_forge_match_resource_types(): @@ -658,7 +658,7 @@ def test_forge_search_by_years(): r1 = check_field(res1, "mdf.year", 2015) == 2 r2 = check_field(res1, "mdf.year", 2016) == 2 r3 = check_field(res1, "mdf.year", 2017) == -1 - assert all(r == True for r in [r1, r2, r3]) # noqa + assert all(r is True for r in [r1, r2, r3]) f2 = forge.Forge() res2 = f2.search_by_years(max=1960, inclusive=False) assert check_field(res2, "mdf.year", 1959) == 2 From 53adc5c7607094a8f9c2b5e6450280a4d62d46e1 Mon Sep 17 00:00:00 2001 From: jgaff Date: Wed, 6 Dec 2017 16:03:25 -0600 Subject: [PATCH 16/22] Range improvements, test improvements The match and exclude range Forge methods now accept None instead of * for unbounded ranges, and will not add anything to the query if the range is *-*. Additionally, tests have had unnecessary Forge instantiations removed. Some tests have been improved for greater coverage. match_years now has a tutorial example. search_by_years was removed. --- .../5 - Field-Specific Helper Functions.ipynb | 359 ++++++++++---- mdf_forge/forge.py | 118 +++-- tests/test_forge.py | 449 +++++++++--------- 3 files changed, 540 insertions(+), 386 deletions(-) diff --git a/docs/tutorials/5 - Field-Specific Helper Functions.ipynb b/docs/tutorials/5 - Field-Specific Helper Functions.ipynb index c1f64bc..a446c90 100644 --- a/docs/tutorials/5 - Field-Specific Helper Functions.ipynb +++ b/docs/tutorials/5 - Field-Specific Helper Functions.ipynb @@ -41,7 +41,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 3, @@ -69,7 +69,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 4, @@ -97,7 +97,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 5, @@ -112,7 +112,9 @@ { "cell_type": "code", "execution_count": 6, - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [ { "data": { @@ -120,29 +122,32 @@ "{'mdf': {'collection': 'OQMD',\n", " 'composition': 'Al2Cu1',\n", " 'elements': ['Al', 'Cu'],\n", - " 'ingest_date': '2017-08-04T14:20:14.773902Z',\n", - " 'links': {'landing_page': 'http://oqmd.org/analysis/calculation/59393',\n", + " 'ingest_date': '2017-08-04T14:19:13.058498Z',\n", + " 'links': {'landing_page': 'http://oqmd.org/analysis/calculation/17664',\n", " 'metadata': {'globus_endpoint': '82f1b5c6-6e9b-11e5-ba47-22000b92c6ec',\n", " 'http_host': 'https://data.materialsdatafacility.org',\n", - " 'path': '/collections/oqmd/data/home/oqmd/libraries/icsd/42517/standard/metadata.json'},\n", + " 'path': '/collections/oqmd/data/home/oqmd/libraries/icsd/107544/static/metadata.json'},\n", " 'outcar': {'globus_endpoint': '82f1b5c6-6e9b-11e5-ba47-22000b92c6ec',\n", " 'http_host': 'https://data.materialsdatafacility.org',\n", - " 'path': '/collections/oqmd/data/home/oqmd/libraries/icsd/42517/standard/OUTCAR'},\n", + " 'path': '/collections/oqmd/data/home/oqmd/libraries/icsd/107544/static/OUTCAR'},\n", " 'parent_id': '5984824ba5ea60170af49754'},\n", - " 'mdf_id': '5984829ea5ea60172af4da9b',\n", + " 'mdf_id': '59848261a5ea60172af4a8c2',\n", " 'metadata_version': '0.3.2',\n", " 'resource_type': 'record',\n", - " 'scroll_id': 17223,\n", + " 'scroll_id': 4462,\n", " 'source_name': 'oqmd',\n", " 'tags': ['metadata', 'outcar'],\n", " 'title': 'OQMD - Al2Cu1'},\n", " 'oqmd': {'band_gap': {'units': 'eV', 'value': 0.0},\n", + " 'configuration': 'static',\n", " 'converged': True,\n", - " 'crossreference': {'icsd': 42517},\n", + " 'crossreference': {'icsd': 107544},\n", + " 'delta_e': {'units': 'eV/atom', 'value': -0.15628721},\n", " 'magnetic_moment': {'units': 'bohr/atom'},\n", - " 'spacegroup': 140,\n", - " 'total_energy': {'units': 'eV/atom', 'value': -3.88954765666667},\n", - " 'volume': {'units': 'angstrom^3/atom', 'value': 14.5666}}}" + " 'spacegroup': 69,\n", + " 'stability': {'units': 'eV/atom', 'value': 0.018707923333333},\n", + " 'total_energy': {'units': 'eV/atom', 'value': -3.89209998333333},\n", + " 'volume': {'units': 'angstrom^3/atom', 'value': 14.7911}}}" ] }, "execution_count": 6, @@ -171,7 +176,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 7, @@ -187,37 +192,42 @@ { "cell_type": "code", "execution_count": 8, - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [ { "data": { "text/plain": [ "[{'mdf': {'collection': 'OQMD',\n", - " 'composition': 'Al3Cu1',\n", + " 'composition': 'Al1Cu3',\n", " 'elements': ['Al', 'Cu'],\n", - " 'ingest_date': '2017-08-04T14:24:39.255975Z',\n", - " 'links': {'landing_page': 'http://oqmd.org/analysis/calculation/214708',\n", + " 'ingest_date': '2017-08-04T14:19:02.238752Z',\n", + " 'links': {'landing_page': 'http://oqmd.org/analysis/calculation/8950',\n", " 'metadata': {'globus_endpoint': '82f1b5c6-6e9b-11e5-ba47-22000b92c6ec',\n", " 'http_host': 'https://data.materialsdatafacility.org',\n", - " 'path': '/collections/oqmd/data/home/oqmd/libraries/prototypes/binaries/D0_3/Al_Cu/standard/metadata.json'},\n", + " 'path': '/collections/oqmd/data/home/oqmd/libraries/icsd/150823/static/metadata.json'},\n", " 'outcar': {'globus_endpoint': '82f1b5c6-6e9b-11e5-ba47-22000b92c6ec',\n", " 'http_host': 'https://data.materialsdatafacility.org',\n", - " 'path': '/collections/oqmd/data/home/oqmd/libraries/prototypes/binaries/D0_3/Al_Cu/standard/OUTCAR'},\n", + " 'path': '/collections/oqmd/data/home/oqmd/libraries/icsd/150823/static/OUTCAR'},\n", " 'parent_id': '5984824ba5ea60170af49754'},\n", - " 'mdf_id': '598483a7a5ea60172af58f7f',\n", + " 'mdf_id': '59848256a5ea60172af49f39',\n", " 'metadata_version': '0.3.2',\n", " 'resource_type': 'record',\n", - " 'scroll_id': 63531,\n", + " 'scroll_id': 2021,\n", " 'source_name': 'oqmd',\n", " 'tags': ['metadata', 'outcar'],\n", - " 'title': 'OQMD - Al3Cu1'},\n", + " 'title': 'OQMD - Al1Cu3'},\n", " 'oqmd': {'band_gap': {'units': 'eV', 'value': 0.0},\n", - " 'configuration': 'standard',\n", + " 'configuration': 'static',\n", " 'converged': True,\n", + " 'crossreference': {'icsd': 150823},\n", + " 'delta_e': {'units': 'eV/atom', 'value': -0.1675233825},\n", " 'magnetic_moment': {'units': 'bohr/atom'},\n", " 'spacegroup': 225,\n", - " 'total_energy': {'units': 'eV/atom', 'value': -3.6518156475},\n", - " 'volume': {'units': 'angstrom^3/atom', 'value': 15.3631}}}]" + " 'stability': {'units': 'eV/atom', 'value': 0.02138741875},\n", + " 'total_energy': {'units': 'eV/atom', 'value': -3.8909277975},\n", + " 'volume': {'units': 'angstrom^3/atom', 'value': 12.3364}}}]" ] }, "execution_count": 8, @@ -249,7 +259,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 9, @@ -264,7 +274,9 @@ { "cell_type": "code", "execution_count": 10, - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [ { "data": { @@ -339,7 +351,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 11, @@ -354,45 +366,54 @@ { "cell_type": "code", "execution_count": 12, - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [ { "data": { "text/plain": [ - "{'mdf': {'author': [{'email': 'c-wolverton@northwestern.edu',\n", - " 'family_name': 'Wolverton',\n", - " 'full_name': 'Chris Wolverton',\n", - " 'given_name': 'Chris',\n", - " 'institution': 'Northwestern University'},\n", - " {'family_name': 'Doak',\n", - " 'full_name': 'Jeff Doak',\n", - " 'given_name': 'Jeff',\n", - " 'institution': 'Northwestern University'}],\n", - " 'citation': ['Doak JW, Wolverton C (2012) Coherent and incoherent phase stabilities of thermoelectric rocksalt IV-VI semiconductor alloys. Phys. Rev. B 86: 144202 http://dx.doi.org/10.1103/PhysRevB.86.144202'],\n", - " 'collection': 'Doak Strain Energies',\n", - " 'data_contact': {'email': 'c-wolverton@northwestern.edu',\n", - " 'family_name': 'Wolverton',\n", - " 'full_name': 'Chris Wolverton',\n", - " 'given_name': 'Chris',\n", - " 'institution': 'Northwestern University'},\n", + "{'mdf': {'author': [{'email': 'mfelling@illinois.edu',\n", + " 'family_name': 'Fellinger',\n", + " 'full_name': 'Michael Fellinger',\n", + " 'given_name': 'Michael',\n", + " 'institution': 'University of Illinois'},\n", + " {'family_name': 'Trinkle',\n", + " 'full_name': 'Dallas Trinkle',\n", + " 'given_name': 'Dallas',\n", + " 'institution': 'University of Illinois'},\n", + " {'family_name': 'Hector Jr.',\n", + " 'full_name': 'Louis Hector Jr.',\n", + " 'given_name': 'Louis',\n", + " 'institution': 'General Motors'}],\n", + " 'citation': ['M. R. Fellinger, L. G. Hector Jr., and D. R. Trinkle, Comp. Mat. Sci. 126, 503 (2017).',\n", + " 'M. R. Fellinger, L. G. Hector Jr., and D. R. Trinkle, Data in Brief 10, 147 (2017).'],\n", + " 'collection': 'Elastic Fe BCC',\n", + " 'data_contact': {'email': 'mfelling@illinois.edu',\n", + " 'family_name': 'Fellinger',\n", + " 'full_name': 'Michael Fellinger',\n", + " 'given_name': 'Michael',\n", + " 'institution': 'University of Illinois'},\n", " 'data_contributor': [{'email': 'jgaff@uchicago.edu',\n", " 'family_name': 'Gaff',\n", " 'full_name': 'Jonathon Gaff',\n", " 'github': 'jgaff',\n", " 'given_name': 'Jonathon',\n", " 'institution': 'The University of Chicago'}],\n", - " 'description': 'We use density functional theory calculations to investigate the coherent and incoherent phase stability of the IV–VI rocksalt semiconductor alloy systems Pb(S,Te), Pb(Te,Se), Pb(Se,S), (Pb,Sn)Te, (Sn,Ge)Te, and (Ge,Pb)Te.',\n", - " 'ingest_date': '2017-08-04T19:21:29.013385Z',\n", - " 'links': {'data_doi': 'http://hdl.handle.net/11256/85',\n", - " 'landing_page': 'https://materialsdata.nist.gov/dspace/xmlui/handle/11256/85',\n", - " 'publication': ['http://dx.doi.org/10.1103/PhysRevB.86.144202']},\n", - " 'mdf_id': '5984c939f2c00437dc2502a5',\n", + " 'description': 'We introduce a solute strain misfit tensor that quantifies how solutes change the lattice parameter.',\n", + " 'ingest_date': '2017-08-04T19:51:21.076837Z',\n", + " 'license': 'http://creativecommons.org/publicdomain/zero/1.0/',\n", + " 'links': {'data_doi': 'http://hdl.handle.net/11256/671',\n", + " 'landing_page': 'https://materialsdata.nist.gov/dspace/xmlui/handle/11256/671',\n", + " 'publication': ['http://dx.doi.org/10.1016/j.commatsci.2016.09.040',\n", + " 'http://dx.doi.org/10.1016/j.dib.2016.11.092']},\n", + " 'mdf_id': '5984d039f2c0043894245abb',\n", " 'metadata_version': '0.3.2',\n", " 'resource_type': 'dataset',\n", - " 'source_name': 'doak_strain_energies',\n", + " 'source_name': 'trinkle_elastic_fe_bcc',\n", " 'tags': ['dft'],\n", - " 'title': 'GeTe-PbTe PbS-PbTe PbSe-PbS PbTe-PbSe PbTe-SnTe SnTe-GeTe mixing and coherency strain energies',\n", - " 'year': 2012}}" + " 'title': 'Ab initio calculations of the lattice parameter and elastic stiffness coefficients of bcc Fe with solutes',\n", + " 'year': 2017}}" ] }, "execution_count": 12, @@ -405,6 +426,157 @@ "res[0]" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### match_years\n", + "`match_years()` matches values against the `\"mdf.year\"` field." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mdf.match_years([\"2015\", 2010])" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'mdf': {'collection': 'PPPDB',\n", + " 'composition': 'C8H8 C7H7N',\n", + " 'description': 'Chi Parameter for poly(styrene) and poly(4-vinylpyridine)',\n", + " 'elements': ['H', 'N', 'C'],\n", + " 'ingest_date': '2017-08-04T20:56:39.045371Z',\n", + " 'links': {'landing_page': 'http://pppdb.uchicago.edu?&id=164',\n", + " 'parent_id': '5984df58f2c004392664b14c',\n", + " 'publication': ['10.1021/ma1006792']},\n", + " 'mdf_id': '5984df87f2c004392664b15e',\n", + " 'metadata_version': '0.3.2',\n", + " 'raw': '{\"doi\": \"10.1021/ma1006792\", \"type\": \"Type 1\", \"temperature\": 160.0, \"tempunit\": \"Celsius\", \"chinumber\": 0.3, \"chierror\": 0.0, \"chia\": 0.0, \"chiaerror\": 0.0, \"chib\": 0.0, \"chiberror\": 0.0, \"chic\": 0.0, \"chicerror\": 0.0, \"notes\": \"\", \"indirect\": 1, \"reference\": \"DOI: 10.1021/ma062516u; DOI: 10.1021/ma702780c\", \"compound1\": \"poly(styrene)\", \"compound2\": \"poly(4-vinylpyridine)\", \"authors\": \"Biplab K. Kuila, E. Bhoje Gowd, and Manfred Stamm\", \"date\": \"August 19, 2010\", \"pppdb_id\": 164}',\n", + " 'resource_type': 'record',\n", + " 'scroll_id': 18,\n", + " 'source_name': 'pppdb',\n", + " 'tags': ['poly(styrene)', 'poly(4-vinylpyridine)'],\n", + " 'title': 'PPPDB - Chi Parameter for poly(styrene) and poly(4-vinylpyridine)',\n", + " 'year': 2010},\n", + " 'pppdb': {'authors': 'Biplab K. Kuila, E. Bhoje Gowd, and Manfred Stamm',\n", + " 'chierror': 0.0,\n", + " 'chinumber': 0.3,\n", + " 'date': 'August 19, 2010',\n", + " 'id': 164,\n", + " 'reference': 'DOI: 10.1021/ma062516u; DOI: 10.1021/ma702780c',\n", + " 'temperature': 160.0,\n", + " 'tempunit': 'Celsius',\n", + " 'type': 'Type 1'}}" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "res = mdf.search(limit=10)\n", + "res[0]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also specify a range of years." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mdf.match_years(start=2014, stop=2016, inclusive=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'mdf': {'collection': 'PPPDB',\n", + " 'composition': 'C7H7N C7H8',\n", + " 'description': 'could not access cited reference for method',\n", + " 'elements': ['H', 'N', 'C'],\n", + " 'ingest_date': '2017-08-04T21:06:57.080792Z',\n", + " 'links': {'landing_page': 'http://pppdb.uchicago.edu?&id=606',\n", + " 'parent_id': '5984df58f2c004392664b14c',\n", + " 'publication': ['10.1021/acs.macromol.5b01261']},\n", + " 'mdf_id': '5984e1f1f2c004392664b2cc',\n", + " 'metadata_version': '0.3.2',\n", + " 'raw': '{\"doi\": \"10.1021/acs.macromol.5b01261\", \"type\": \"Type 1\", \"temperature\": 0.0, \"tempunit\": \"\", \"chinumber\": 1.01, \"chierror\": 0.0, \"chia\": 0.0, \"chiaerror\": 0.0, \"chib\": 0.0, \"chiberror\": 0.0, \"chic\": 0.0, \"chicerror\": 0.0, \"notes\": \"could not access cited reference for method\", \"indirect\": 1, \"reference\": \"ISBN: 0-471-16628-6\", \"compound1\": \"poly(4-vinylpyridine)\", \"compound2\": \"toluene\", \"authors\": \"Renhua Deng, Hui Li, Fuxin Liang, Jintao Zhu, Baohui Li, Xiaolin Xie, and Zhenzhong Yang \", \"date\": \"August 6, 2015\", \"pppdb_id\": 606}',\n", + " 'resource_type': 'record',\n", + " 'scroll_id': 384,\n", + " 'source_name': 'pppdb',\n", + " 'tags': ['poly(4-vinylpyridine)', 'toluene'],\n", + " 'title': 'PPPDB - Chi Parameter for poly(4-vinylpyridine) and toluene',\n", + " 'year': 2015},\n", + " 'pppdb': {'authors': 'Renhua Deng, Hui Li, Fuxin Liang, Jintao Zhu, Baohui Li, Xiaolin Xie, and Zhenzhong Yang ',\n", + " 'chierror': 0.0,\n", + " 'chinumber': 1.01,\n", + " 'date': 'August 6, 2015',\n", + " 'id': 606,\n", + " 'reference': 'ISBN: 0-471-16628-6',\n", + " 'temperature': 0.0,\n", + " 'tempunit': '',\n", + " 'type': 'Type 1'}}" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "res = mdf.search(limit=10)\n", + "res[0]" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -422,7 +594,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 17, "metadata": { "scrolled": true }, @@ -431,34 +603,37 @@ "data": { "text/plain": [ "{'mdf': {'collection': 'OQMD',\n", - " 'composition': 'Al3Cu1',\n", + " 'composition': 'Al2Cu1',\n", " 'elements': ['Al', 'Cu'],\n", - " 'ingest_date': '2017-08-04T14:24:39.255975Z',\n", - " 'links': {'landing_page': 'http://oqmd.org/analysis/calculation/214708',\n", + " 'ingest_date': '2017-08-04T14:19:13.058498Z',\n", + " 'links': {'landing_page': 'http://oqmd.org/analysis/calculation/17664',\n", " 'metadata': {'globus_endpoint': '82f1b5c6-6e9b-11e5-ba47-22000b92c6ec',\n", " 'http_host': 'https://data.materialsdatafacility.org',\n", - " 'path': '/collections/oqmd/data/home/oqmd/libraries/prototypes/binaries/D0_3/Al_Cu/standard/metadata.json'},\n", + " 'path': '/collections/oqmd/data/home/oqmd/libraries/icsd/107544/static/metadata.json'},\n", " 'outcar': {'globus_endpoint': '82f1b5c6-6e9b-11e5-ba47-22000b92c6ec',\n", " 'http_host': 'https://data.materialsdatafacility.org',\n", - " 'path': '/collections/oqmd/data/home/oqmd/libraries/prototypes/binaries/D0_3/Al_Cu/standard/OUTCAR'},\n", + " 'path': '/collections/oqmd/data/home/oqmd/libraries/icsd/107544/static/OUTCAR'},\n", " 'parent_id': '5984824ba5ea60170af49754'},\n", - " 'mdf_id': '598483a7a5ea60172af58f7f',\n", + " 'mdf_id': '59848261a5ea60172af4a8c2',\n", " 'metadata_version': '0.3.2',\n", " 'resource_type': 'record',\n", - " 'scroll_id': 63531,\n", + " 'scroll_id': 4462,\n", " 'source_name': 'oqmd',\n", " 'tags': ['metadata', 'outcar'],\n", - " 'title': 'OQMD - Al3Cu1'},\n", + " 'title': 'OQMD - Al2Cu1'},\n", " 'oqmd': {'band_gap': {'units': 'eV', 'value': 0.0},\n", - " 'configuration': 'standard',\n", + " 'configuration': 'static',\n", " 'converged': True,\n", + " 'crossreference': {'icsd': 107544},\n", + " 'delta_e': {'units': 'eV/atom', 'value': -0.15628721},\n", " 'magnetic_moment': {'units': 'bohr/atom'},\n", - " 'spacegroup': 225,\n", - " 'total_energy': {'units': 'eV/atom', 'value': -3.6518156475},\n", - " 'volume': {'units': 'angstrom^3/atom', 'value': 15.3631}}}" + " 'spacegroup': 69,\n", + " 'stability': {'units': 'eV/atom', 'value': 0.018707923333333},\n", + " 'total_energy': {'units': 'eV/atom', 'value': -3.89209998333333},\n", + " 'volume': {'units': 'angstrom^3/atom', 'value': 14.7911}}}" ] }, - "execution_count": 13, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -478,8 +653,10 @@ }, { "cell_type": "code", - "execution_count": 14, - "metadata": {}, + "execution_count": 18, + "metadata": { + "scrolled": true + }, "outputs": [ { "data": { @@ -514,7 +691,7 @@ " 'year': 1985}}" ] }, - "execution_count": 14, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -534,8 +711,10 @@ }, { "cell_type": "code", - "execution_count": 15, - "metadata": {}, + "execution_count": 19, + "metadata": { + "scrolled": true + }, "outputs": [ { "data": { @@ -580,7 +759,7 @@ " 'year': 2014}}" ] }, - "execution_count": 15, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -600,42 +779,44 @@ }, { "cell_type": "code", - "execution_count": 16, - "metadata": {}, + "execution_count": 20, + "metadata": { + "scrolled": true + }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "100%|██████████| 1246/1246 [00:01<00:00, 669.40it/s]\n" + "1401it [00:01, 787.20it/s] \n" ] }, { "data": { "text/plain": [ - "{'fe_cr_al_oxidation': {'atomic_composition_percent': {'Al': 4.5,\n", - " 'Cr': 25.7,\n", - " 'Fe': 69.8},\n", + "{'fe_cr_al_oxidation': {'atomic_composition_percent': {'Al': 3.1,\n", + " 'Cr': 26.5,\n", + " 'Fe': 70.5},\n", " 'temperature_k': 420.0},\n", " 'mdf': {'collection': 'Fe-Cr-Al Oxidation Studies',\n", " 'composition': 'FeCrAl',\n", " 'elements': ['Cr', 'Fe', 'Al'],\n", - " 'ingest_date': '2017-08-04T21:26:53.266642Z',\n", + " 'ingest_date': '2017-08-04T21:26:53.296946Z',\n", " 'links': {'csv': {'globus_endpoint': '82f1b5c6-6e9b-11e5-ba47-22000b92c6ec',\n", " 'http_host': 'https://data.materialsdatafacility.org',\n", - " 'path': '/collections/Fe_Cr_Al_data/420 K/420 K Point 100.txt'},\n", - " 'landing_page': 'https://materialsdata.nist.gov/dspace/xmlui/handle/11256/836#100',\n", + " 'path': '/collections/Fe_Cr_Al_data/420 K/420 K Point 104.txt'},\n", + " 'landing_page': 'https://materialsdata.nist.gov/dspace/xmlui/handle/11256/836#104',\n", " 'parent_id': '5984e69cf2c00439c790bf54'},\n", - " 'mdf_id': '5984e69df2c00439c790bfb8',\n", + " 'mdf_id': '5984e69df2c00439c790bfbc',\n", " 'metadata_version': '0.3.2',\n", " 'resource_type': 'record',\n", - " 'scroll_id': 100,\n", + " 'scroll_id': 104,\n", " 'source_name': 'fe_cr_al_oxidation',\n", " 'tags': ['csv'],\n", - " 'title': 'Fe-Cr-Al Oxidation - 420 K Point 100'}}" + " 'title': 'Fe-Cr-Al Oxidation - 420 K Point 104'}}" ] }, - "execution_count": 16, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } diff --git a/mdf_forge/forge.py b/mdf_forge/forge.py index 1ea7999..ac9b966 100644 --- a/mdf_forge/forge.py +++ b/mdf_forge/forge.py @@ -95,7 +95,7 @@ def match_field(self, field, value, required=True, new_group=False): self.__query.and_join(new_group) else: self.__query.or_join(new_group) - self.__query.field(field, value) + self.__query.field(str(field), str(value)) return self def exclude_field(self, field, value, new_group=False): @@ -118,7 +118,7 @@ def exclude_field(self, field, value, new_group=False): # OR would not make much sense for excluding if self.__query.initialized: self.__query.and_join(new_group) - self.__query.negate().field(field, value) + self.__query.negate().field(str(field), str(value)) return self def search(self, q=None, advanced=False, limit=SEARCH_LIMIT, info=False, @@ -221,7 +221,8 @@ def reset_query(self): # * Expanded functions # *********************************************** - def match_range(self, field, start, stop, inclusive=True, required=True, new_group=False): + def match_range(self, field, start="*", stop="*", inclusive=True, + required=True, new_group=False): """Add a field:[some range] term to the query. Matches will have field == value in range. @@ -241,6 +242,15 @@ def match_range(self, field, start, stop, inclusive=True, required=True, new_gro Returns: self (Forge): For chaining. """ + # Accept None as * + if start is None: + start = "*" + if stop is None: + stop = "*" + # No-op on *-* + if start == "*" and stop == "*": + return self + if inclusive: value = "[" + str(start) + " TO " + str(stop) + "]" else: @@ -248,7 +258,8 @@ def match_range(self, field, start, stop, inclusive=True, required=True, new_gro self.match_field(field, value, required=required, new_group=new_group) return self - def exclude_range(self, field, start, stop, inclusive=True, new_group=False): + def exclude_range(self, field, start="*", stop="*", inclusive=True, + required=True, new_group=False): """Exclude a field:[some range] term to the query. Matches will have field != values in range. @@ -267,6 +278,15 @@ def exclude_range(self, field, start, stop, inclusive=True, new_group=False): Returns: self (Forge): For chaining. """ + # Accept None as * + if start is None: + start = "*" + if stop is None: + stop = "*" + # No-op on *-* + if start == "*" and stop == "*": + return self + if inclusive: value = "[" + str(start) + " TO " + str(stop) + "]" else: @@ -421,65 +441,57 @@ def match_titles(self, titles): self.match_field(field="mdf.title", value=title, required=False, new_group=False) return self - def match_years(self, years=None, min=None, max=None, inclusive=True): + def match_years(self, years=None, start=None, stop=None, inclusive=True): """Add years and limits to the query. Arguments: years (int or string, or list of int or strings): The years to match. - min (int or string): The lower range of years to match. - max (int or string): The upper range of years to match. - inclusive (bool): If True, will add min and max years to the range. + Note that this argument overrides the start, stop, and inclusive arguments. + start (int or string): The lower range of years to match. + stop (int or string): The upper range of years to match. + inclusive (bool): If True, the start and stop values will be included in the search. If False, they will be excluded. Default True. - limit (int): The maximum number of results to return. - The max for this argument is the SEARCH_LIMIT imposed by Globus Search. - info (bool): If False, search will return a list of the results. - If True, search will return a tuple containing the results list, - and other information about the query. Default False. Returns: self (Forge): For chaining. """ - if years is None and min is None and max is None: - print_("Year is not a valid input.") + # If nothing supplied, nothing to match + if years is None and start is None and stop is None: return self - if years is not None: - if not years: - return self + if years is not None and years != []: if not isinstance(years, list): years = [years] - years_new = [] + years_int = [] for year in years: try: - year = int(year) - print(year) - years_new.append(year) + y_int = int(year) + years_int.append(y_int) except ValueError: - print_("Year is not a valid input.") - return self - - self.match_field(field="mdf.year", value=str(years_new[0]), required=True, - new_group=True) - for year in years_new[1:]: - self.match_field(field="mdf.year", value=str(year), required=False, new_group=False) + print_("Invalid year: '", year, "'", sep="") + + # Only match years if valid years were supplied + if len(years_int) > 0: + self.match_field(field="mdf.year", value=years_int[0], required=True, + new_group=True) + for year in years_int[1:]: + self.match_field(field="mdf.year", value=year, required=False, new_group=False) else: - year_start = "*" - year_stop = "*" - if min is not None: + if start is not None: try: - year_start = int(min) + start = int(start) except ValueError: - print_("Year is not a valid input.") - return self - if max is not None: + print_("Invalid start year: '", start, "'", sep="") + start = None + if stop is not None: try: - year_stop = int(max) + stop = int(stop) except ValueError: - print_("Year is not a valid input.") - return self + print_("Invalid stop year: '", stop, "'", sep="") + stop = None - self.match_range(field="mdf.year", start=str(year_start), stop=str(year_stop), - inclusive=inclusive, required=True, new_group=False) + self.match_range(field="mdf.year", start=start, stop=stop, + inclusive=inclusive, required=True, new_group=True) return self def match_resource_types(self, types): @@ -576,30 +588,6 @@ def search_by_tags(self, tags, limit=None, match_all=True, info=False): """ return self.match_tags(tags, match_all=match_all).search(limit=limit, info=info) - def search_by_years(self, years=None, min=None, max=None, inclusive=True, limit=None, - info=False): - """Execute a search for the given year or years. - search_by_years([x]) is equivalent to match_years([x]).search() - - Arguments: - years (int or string, or list of int or strings): The years to match. - min (int or string): The lower range of years to match. - max (int or string): The upper range of years to match. - inclusive (bool): If True, will add min and max years to the range. - If False, they will be excluded. - Default True. - limit (int): The maximum number of results to return. - The max for this argument is the SEARCH_LIMIT imposed by Globus Search. - info (bool): If False, search will return a list of the results. - If True, search will return a tuple containing the results list, - and other information about the query. Default False. - - Returns: - list (if info=False): The results. - tuple (if info=True): The results, and a dictionary of query information. - """ - return self.match_years(years, min, max, inclusive=inclusive).search(limit=limit, info=info) - def aggregate_source(self, sources): """Aggregate all records from a given source. There is no limit to the number of results returned. diff --git a/tests/test_forge.py b/tests/test_forge.py index a81691f..baf9f3a 100644 --- a/tests/test_forge.py +++ b/tests/test_forge.py @@ -72,107 +72,102 @@ def test_query_field(): def test_query_operator(capsys): - q1 = forge.Query(query_search_client) - assert q1.query == "(" + q = forge.Query(query_search_client) + assert q.query == "(" # Add bad operator - assert q1.operator("FOO") == q1 + assert q.operator("FOO") == q out, err = capsys.readouterr() assert "Error: 'FOO' is not a valid operator" - assert q1.query == "(" + assert q.query == "(" # Test operator cleaning - q1.operator(" and ") - assert q1.query == "( AND " + q.operator(" and ") + assert q.query == "( AND " # Test close_group - q1.operator("OR", close_group=True) - assert q1.query == "( AND ) OR (" + q.operator("OR", close_group=True) + assert q.query == "( AND ) OR (" def test_query_and_join(capsys): - q1 = forge.Query(query_search_client) + q = forge.Query(query_search_client) # Test not initialized - assert q1.and_join() == q1 + assert q.and_join() == q out, err = capsys.readouterr() assert ("Error: You must add a term before adding an operator. " "The current query has not been changed.") in out # Regular join - q1.term("foo").and_join() - assert q1.query == "(foo AND " + q.term("foo").and_join() + assert q.query == "(foo AND " # close_group - q1.term("bar").and_join(close_group=True) - assert q1.query == "(foo AND bar) AND (" + q.term("bar").and_join(close_group=True) + assert q.query == "(foo AND bar) AND (" def test_query_or_join(capsys): - q1 = forge.Query(query_search_client) + q = forge.Query(query_search_client) # Test not initialized - assert q1.or_join() == q1 + assert q.or_join() == q out, err = capsys.readouterr() assert ("Error: You must add a term before adding an operator. " "The current query has not been changed.") in out # Regular join - q1.term("foo").or_join() - assert q1.query == "(foo OR " + q.term("foo").or_join() + assert q.query == "(foo OR " # close_group - q1.term("bar").or_join(close_group=True) - assert q1.query == "(foo OR bar) OR (" + q.term("bar").or_join(close_group=True) + assert q.query == "(foo OR bar) OR (" def test_query_search(capsys): # Error on no query - q1 = forge.Query(query_search_client) - assert q1.search() == [] + q = forge.Query(query_search_client) + assert q.search() == [] out, err = capsys.readouterr() assert "Error: No query" in out - assert q1.search(info=True) == ([], {"error": "No query"}) + assert q.search(info=True) == ([], {"error": "No query"}) # Return info if requested - q2 = forge.Query(query_search_client) - res2 = q2.search(q="Al", info=False) + res2 = q.search(q="Al", info=False) assert isinstance(res2, list) assert isinstance(res2[0], dict) - q3 = forge.Query(query_search_client) - res3 = q3.search(q="Al", info=True) + res3 = q.search(q="Al", info=True) assert isinstance(res3, tuple) assert isinstance(res3[0], list) assert isinstance(res3[0][0], dict) assert isinstance(res3[1], dict) # Check limit - q4 = forge.Query(query_search_client) - res4 = q4.search("oqmd", limit=3) + res4 = q.search("oqmd", limit=3) assert len(res4) == 3 # Check limit correction - q5 = forge.Query(query_search_client) - res5 = q5.search("nist_xps_db", limit=20000) + res5 = q.search("nist_xps_db", limit=20000) assert len(res5) == 10000 def test_query_aggregate(capsys): - q1 = forge.Query(query_search_client) + q = forge.Query(query_search_client) # Error on no query - assert q1.aggregate() == [] + assert q.aggregate() == [] out, err = capsys.readouterr() assert "Error: No query" in out # Basic aggregation - res1 = q1.aggregate("mdf.source_name:nist_xps_db") + res1 = q.aggregate("mdf.source_name:nist_xps_db") assert len(res1) > 10000 assert isinstance(res1[0], dict) # Multi-dataset aggregation - q2 = forge.Query(query_search_client) - res2 = q2.aggregate("(mdf.source_name:nist_xps_db OR mdf.source_name:nist_janaf)") + res2 = q.aggregate("(mdf.source_name:nist_xps_db OR mdf.source_name:nist_janaf)") assert len(res2) > 10000 assert len(res2) > len(res1) def test_query_chaining(): - q1 = forge.Query(query_search_client) - q1.field("source_name", "cip") - q1.and_join() - q1.field("elements", "Al") - res1 = q1.search(limit=10000) + q = forge.Query(query_search_client) + q.field("source_name", "cip") + q.and_join() + q.field("elements", "Al") + res1 = q.search(limit=10000) res2 = (forge.Query(query_search_client) .field("source_name", "cip") .and_join() @@ -209,10 +204,10 @@ def test_query_cleaning(): # Test properties def test_forge_properties(): - f1 = forge.Forge() - assert type(f1.search_client) is toolbox.SearchClient - assert type(f1.transfer_client) is globus_sdk.TransferClient - assert type(f1.mdf_authorizer) is globus_sdk.RefreshTokenAuthorizer + f = forge.Forge() + assert type(f.search_client) is toolbox.SearchClient + assert type(f.transfer_client) is globus_sdk.TransferClient + assert type(f.mdf_authorizer) is globus_sdk.RefreshTokenAuthorizer # Sample results for download testing @@ -340,79 +335,85 @@ def check_field(res, field, value): def test_forge_match_field(): - f1 = forge.Forge() + f = forge.Forge() # Basic usage - f1.match_field("mdf.source_name", "nist_janaf") - res1 = f1.search() + f.match_field("mdf.source_name", "nist_janaf") + res1 = f.search() assert check_field(res1, "mdf.source_name", "nist_janaf") == 0 # Check that query clears - assert f1.search() == [] + assert f.search() == [] + # Also checking check_field - f2 = forge.Forge() - f2.match_field("mdf.elements", "Al") - res2 = f2.search() + f.match_field("mdf.elements", "Al") + res2 = f.search() assert check_field(res2, "mdf.elements", "Al") == 1 def test_forge_exclude_field(): - f1 = forge.Forge() + f = forge.Forge() # Basic usage - f1.exclude_field("mdf.elements", "Al") - f1.match_field("mdf.source_name", "core_mof") - res1 = f1.search() + f.exclude_field("mdf.elements", "Al") + f.match_field("mdf.source_name", "core_mof") + res1 = f.search() assert check_field(res1, "mdf.elements", "Al") == -1 def test_forge_match_range(): # Single-value use - f1 = forge.Forge() - f1.match_range("mdf.elements", "Al", "Al") - res1, info1 = f1.search(info=True) + f = forge.Forge() + f.match_range("mdf.elements", "Al", "Al") + res1, info1 = f.search(info=True) assert check_field(res1, "mdf.elements", "Al") == 1 - f2 = forge.Forge() - res2, info2 = f2.search("mdf.elements:Al", advanced=True, info=True) + + res2, info2 = f.search("mdf.elements:Al", advanced=True, info=True) assert info1["total_query_matches"] == info2["total_query_matches"] + # Non-matching use, test inclusive - f3 = forge.Forge() - f3.match_range("mdf.elements", "Al", "Al", inclusive=False) - assert f3.search() == [] + f.match_range("mdf.elements", "Al", "Al", inclusive=False) + assert f.search() == [] + # Actual range - f4 = forge.Forge() - f4.match_range("mdf.elements", "Al", "Cu") - res4, info4 = f4.search(info=True) + f.match_range("mdf.elements", "Al", "Cu") + res4, info4 = f.search(info=True) assert info1["total_query_matches"] < info4["total_query_matches"] assert (check_field(res4, "mdf.elements", "Al") >= 0 or check_field(res4, "mdf.elements", "Cu") >= 0) + # Nothing to match + assert f.match_range("field", start=None, stop=None) == f + def test_forge_exclude_range(): # Single-value use - f1 = forge.Forge() - f1.exclude_range("mdf.elements", "Am", "*") - f1.exclude_range("mdf.elements", "*", "Ak") - res1, info1 = f1.search(info=True) + f = forge.Forge() + f.exclude_range("mdf.elements", "Am", "*") + f.exclude_range("mdf.elements", "*", "Ak") + res1, info1 = f.search(info=True) assert (check_field(res1, "mdf.elements", "Al") == 0 or check_field(res1, "mdf.elements", "Al") == 2) - f2 = forge.Forge() - res2, info2 = f2.search("mdf.elements:Al", advanced=True, info=True) + + res2, info2 = f.search("mdf.elements:Al", advanced=True, info=True) assert info1["total_query_matches"] <= info2["total_query_matches"] + # Non-matching use, test inclusive - f3 = forge.Forge() - f3.exclude_range("mdf.elements", "Am", "*") - f3.exclude_range("mdf.elements", "*", "Ak") - f3.exclude_range("mdf.elements", "Al", "Al", inclusive=False) - res3, info3 = f3.search(info=True) + f.exclude_range("mdf.elements", "Am", "*") + f.exclude_range("mdf.elements", "*", "Ak") + f.exclude_range("mdf.elements", "Al", "Al", inclusive=False) + res3, info3 = f.search(info=True) assert info1["total_query_matches"] == info3["total_query_matches"] + # Nothing to match + assert f.exclude_range("field", start=None, stop=None) == f + def test_forge_exclusive_match(): - f1 = forge.Forge() - f1.exclusive_match("mdf.elements", "Al") - res1 = f1.search() + f = forge.Forge() + f.exclusive_match("mdf.elements", "Al") + res1 = f.search() assert check_field(res1, "mdf.elements", "Al") == 0 - f2 = forge.Forge() - f2.exclusive_match("mdf.elements", ["Al", "Cu"]) - res2 = f2.search() + + f.exclusive_match("mdf.elements", ["Al", "Cu"]) + res2 = f.search() assert check_field(res2, "mdf.elements", "Al") == 1 assert check_field(res2, "mdf.elements", "Cu") == 1 assert check_field(res2, "mdf.elements", "Cp") == -1 @@ -420,110 +421,106 @@ def test_forge_exclusive_match(): def test_forge_match_sources(): - f1 = forge.Forge() + f = forge.Forge() # One source - f1.match_sources("nist_janaf") - res1 = f1.search() + f.match_sources("nist_janaf") + res1 = f.search() assert res1 != [] assert check_field(res1, "mdf.source_name", "nist_janaf") == 0 + # Multi-source - f2 = forge.Forge() - f2.match_sources(["nist_janaf", "hopv"]) - res2 = f2.search() + f.match_sources(["nist_janaf", "hopv"]) + res2 = f.search() # res1 is a subset of res2 assert len(res2) > len(res1) assert all([r1 in res2 for r1 in res1]) assert check_field(res2, "mdf.source_name", "nist_janaf") == 2 + # No source - f3 = forge.Forge() - assert f3.match_sources("") == f3 + assert f.match_sources("") == f def test_forge_match_ids(): # Get a couple IDs - f0 = forge.Forge() - res0 = f0.search("mdf.source_name:nist_janaf", advanced=True, limit=2) + f = forge.Forge() + res0 = f.search("mdf.source_name:nist_janaf", advanced=True, limit=2) id1 = res0[0]["mdf"]["mdf_id"] id2 = res0[1]["mdf"]["mdf_id"] - f1 = forge.Forge() + # One ID - f1.match_ids(id1) - res1 = f1.search() + f.match_ids(id1) + res1 = f.search() assert res1 != [] assert check_field(res1, "mdf.mdf_id", id1) == 0 + # Multi-ID - f2 = forge.Forge() - f2.match_ids([id1, id2]) - res2 = f2.search() + f.match_ids([id1, id2]) + res2 = f.search() # res1 is a subset of res2 assert len(res2) > len(res1) assert all([r1 in res2 for r1 in res1]) assert check_field(res2, "mdf.mdf_id", id2) == 2 + # No id - f3 = forge.Forge() - assert f3.match_ids("") == f3 + assert f.match_ids("") == f def test_forge_match_elements(): - f1 = forge.Forge() + f = forge.Forge() # One element - f1.match_elements("Al") - res1 = f1.search() + f.match_elements("Al") + res1 = f.search() assert res1 != [] check_val1 = check_field(res1, "mdf.elements", "Al") assert check_val1 == 0 or check_val1 == 1 + # Multi-element - f2 = forge.Forge() - f2.match_elements(["Al", "Cu"]) - res2 = f2.search() + f.match_elements(["Al", "Cu"]) + res2 = f.search() assert check_field(res2, "mdf.elements", "Al") == 1 assert check_field(res2, "mdf.elements", "Cu") == 1 + # No elements - f3 = forge.Forge() - assert f3.match_elements("") == f3 + assert f.match_elements("") == f def test_forge_match_titles(): # One title - f1 = forge.Forge() + f = forge.Forge() titles1 = '"OQMD - Na1Y2Zr1"' - res1 = f1.match_titles(titles1).search() + res1 = f.match_titles(titles1).search() assert res1 != [] assert check_field(res1, "mdf.title", "OQMD - Na1Y2Zr1") == 0 # Multiple titles - f2 = forge.Forge() titles2 = ['"AMCS - Tungsten"', '"Cytochrome QSAR"'] - res2 = f2.match_titles(titles2).search() + res2 = f.match_titles(titles2).search() assert res2 != [] assert check_field(res2, "mdf.title", "Cytochrome QSAR - C13F2N6O") == 2 # No titles - f3 = forge.Forge() - assert f3.match_titles("") == f3 + assert f.match_titles("") == f def test_forge_match_tags(): # Get one tag - f0 = forge.Forge() - res0 = f0.search("mdf.source_name:trinkle_elastic_fe_bcc", advanced=True, limit=1) + f = forge.Forge() + res0 = f.search("mdf.source_name:trinkle_elastic_fe_bcc", advanced=True, limit=1) tags1 = res0[0]["mdf"]["tags"][0] + # One tag - f1 = forge.Forge() - res1 = f1.match_tags(tags1).search() + res1 = f.match_tags(tags1).search() assert check_field(res1, "mdf.tags", tags1) == 2 - f2 = forge.Forge() tags2 = "\"ab initio\"" - f2.match_tags(tags2) - res2 = f2.search() + f.match_tags(tags2) + res2 = f.search() # Elasticsearch splits ["ab-initio"] into ["ab", "initio"] assert check_field(res2, "mdf.tags", "ab-initio") == 2 # Multiple tags - f3 = forge.Forge() tags3 = ["\"density functional theory calculations\"", "\"X-ray\""] - res3 = f3.match_tags(tags3, match_all=True).search() + res3 = f.match_tags(tags3, match_all=True).search() # "source_name": "ge_nanoparticles", # "tags": [ "amorphization","density functional theory calculations","Ge nanoparticles", # "high pressure","phase transformation","Raman","X-ray absorption","zip" ] @@ -532,145 +529,132 @@ def test_forge_match_tags(): assert check_field(res3, "mdf.tags", "density functional theory calculations") == 1 # No tag - f4 = forge.Forge() - assert f4.match_tags("") == f4 + assert f.match_tags("") == f def test_forge_match_years(capfd): # One year of data/results - f1 = forge.Forge() - years1 = ["2015"] - res1 = f1.match_years(years1).search(limit=10) + f = forge.Forge() + years1 = "2015" + res1 = f.match_years(years1).search(limit=10) assert res1 != [] assert check_field(res1, "mdf.year", 2015) == 0 # Multiple years - f2 = forge.Forge() - years2 = [2015, "2011"] - # match_all=False (2011 OR 2015) - res2 = f2.match_years(years2, inclusive=False).search() + years2 = ["2015", 2011] + res2 = f.match_years(years2).search() assert check_field(res2, "mdf.year", 2011) == 2 # Wrong input - f3 = forge.Forge() years3 = ["20x5"] - f3.match_years(years3, inclusive=False).search() + f.match_years(years3).search() out, err = capfd.readouterr() - msg_err = "Year is not a valid input" in out - assert msg_err is True + assert "Invalid year: '20x5'" in out + + # Test range + res4 = f.match_years(start=2015, stop=2015, inclusive=True).search() + assert check_field(res4, "mdf.year", 2015) == 0 + + res5 = f.match_years(start=2014, stop=2017, inclusive=False).search() + assert check_field(res5, "mdf.year", 2013) == -1 + assert check_field(res5, "mdf.year", 2014) == -1 + assert check_field(res5, "mdf.year", 2015) == 2 + assert check_field(res5, "mdf.year", 2016) == 2 + assert check_field(res5, "mdf.year", 2017) == -1 + + assert f.match_years(start=2015, stop=2015, inclusive=False).search() == [] def test_forge_match_resource_types(): - f1 = forge.Forge() + f = forge.Forge() # Test one type - f1.match_resource_types("record") - res1 = f1.search(limit=10) + f.match_resource_types("record") + res1 = f.search(limit=10) assert check_field(res1, "mdf.resource_type", "record") == 0 + # Test two types - f2 = forge.Forge() - f2.match_resource_types(["collection", "dataset"]) - res2 = f2.search() + f.match_resource_types(["collection", "dataset"]) + res2 = f.search() assert check_field(res2, "mdf.resource_type", "record") == -1 # TODO: Re-enable this assert after we get collections in MDF # assert check_field(res2, "mdf.resource_type", "dataset") == 2 + # Test zero types - f3 = forge.Forge() - assert f3.match_resource_types("") == f3 + assert f.match_resource_types("") == f def test_forge_search(capsys): # Error on no query - f1 = forge.Forge() - assert f1.search() == [] + f = forge.Forge() + assert f.search() == [] out, err = capsys.readouterr() assert "Error: No query" in out # Return info if requested - f2 = forge.Forge() - res2 = f2.search(q="Al", info=False) + res2 = f.search(q="Al", info=False) assert isinstance(res2, list) assert isinstance(res2[0], dict) - f3 = forge.Forge() - res3 = f3.search(q="Al", info=True) + + res3 = f.search(q="Al", info=True) assert isinstance(res3, tuple) assert isinstance(res3[0], list) assert isinstance(res3[0][0], dict) assert isinstance(res3[1], dict) # Check limit - f4 = forge.Forge() - res4 = f4.search("oqmd", limit=3) + res4 = f.search("oqmd", limit=3) assert len(res4) == 3 # Check reset_query - f5 = forge.Forge() - f5.match_field("mdf.source_name", "hopv") - res5 = f5.search(reset_query=False) - res6 = f5.search() + f.match_field("mdf.source_name", "hopv") + res5 = f.search(reset_query=False) + res6 = f.search() assert all([r in res6 for r in res5]) and all([r in res5 for r in res6]) def test_forge_search_by_elements(): - f1 = forge.Forge() - f2 = forge.Forge() + f = forge.Forge() elements = ["Cu", "Al"] sources = ["oqmd", "nist_xps_db"] - res1, info1 = f1.match_sources(sources).match_elements(elements).search(limit=10000, info=True) - res2, info2 = f2.search_by_elements(elements, sources, limit=10000, info=True) + res1, info1 = f.match_sources(sources).match_elements(elements).search(limit=10000, info=True) + res2, info2 = f.search_by_elements(elements, sources, limit=10000, info=True) assert all([r in res2 for r in res1]) and all([r in res1 for r in res2]) assert check_field(res1, "mdf.elements", "Al") == 1 assert check_field(res1, "mdf.source_name", "oqmd") == 2 def test_forge_search_by_titles(): - f1 = forge.Forge() + f = forge.Forge() titles1 = ["\"AMCS - Tungsten\""] - res1 = f1.search_by_titles(titles1) + res1 = f.search_by_titles(titles1) assert check_field(res1, "mdf.title", "AMCS - Tungsten") == 0 - f2 = forge.Forge() titles2 = ["Tungsten"] - res2 = f2.search_by_titles(titles2) + res2 = f.search_by_titles(titles2) assert check_field(res2, "mdf.title", "AMCS - Tungsten") == 2 def test_forge_search_by_tags(): - f1 = forge.Forge() + f = forge.Forge() tags1 = "DFT" - res1 = f1.search_by_tags(tags1) + res1 = f.search_by_tags(tags1) assert check_field(res1, "mdf.tags", "DFT") == 2 - f2 = forge.Forge() tags2 = ["\"Density Functional Theory\"", "\"X-ray\""] - res2 = f2.search_by_tags(tags2, match_all=True) - f3 = forge.Forge() + res2 = f.search_by_tags(tags2, match_all=True) + tags3 = ["\"Density Functional Theory\"", "\"X-ray\""] - res3 = f3.search_by_tags(tags3, match_all=False) + res3 = f.search_by_tags(tags3, match_all=False) # res2 is a subset of res3 assert len(res3) > len(res2) assert all([r in res3 for r in res2]) -def test_forge_search_by_years(): - f1 = forge.Forge() - res1 = f1.search_by_years(min=2015, max=2016) - r1 = check_field(res1, "mdf.year", 2015) == 2 - r2 = check_field(res1, "mdf.year", 2016) == 2 - r3 = check_field(res1, "mdf.year", 2017) == -1 - assert all(r is True for r in [r1, r2, r3]) - f2 = forge.Forge() - res2 = f2.search_by_years(max=1960, inclusive=False) - assert check_field(res2, "mdf.year", 1959) == 2 - f3 = forge.Forge() - res3 = f3.search_by_years(min=-0, max="-10", inclusive=True) - assert check_field(res3, "mdf.year", 2010) == -1 - - def test_forge_aggregate_source(): # Test limit - f1 = forge.Forge() - res1 = f1.aggregate_source("amcs") + f = forge.Forge() + res1 = f.aggregate_source("amcs") assert isinstance(res1, list) assert len(res1) > 10000 assert isinstance(res1[0], dict) @@ -678,63 +662,60 @@ def test_forge_aggregate_source(): def test_forge_fetch_datasets_from_results(): # Get some results + f = forge.Forge() # Record from OQMD - res01 = forge.Forge().search("mdf.source_name:oqmd AND mdf.resource_type:record", - advanced=True, limit=1) + res01 = f.search("mdf.source_name:oqmd AND mdf.resource_type:record", advanced=True, limit=1) # Record from OQMD with info - res02 = forge.Forge().search("mdf.source_name:oqmd AND mdf.resource_type:record", - advanced=True, limit=1, info=True) + res02 = f.search("mdf.source_name:oqmd AND mdf.resource_type:record", + advanced=True, limit=1, info=True) # Records from JANAF - res03 = forge.Forge().search("mdf.source_name:nist_janaf AND mdf.resource_type:record", - advanced=True, limit=2) + res03 = f.search("mdf.source_name:nist_janaf AND mdf.resource_type:record", + advanced=True, limit=2) # Dataset for NIST XPS DB - res04 = forge.Forge().search("mdf.source_name:nist_xps_db AND mdf.resource_type:dataset", - advanced=True) + res04 = f.search("mdf.source_name:nist_xps_db AND mdf.resource_type:dataset", advanced=True) # Get the correct dataset entries - oqmd = forge.Forge().search("mdf.source_name:oqmd AND mdf.resource_type:dataset", - advanced=True)[0] - nist_janaf = forge.Forge().search("mdf.source_name:nist_janaf AND mdf.resource_type:dataset", - advanced=True)[0] + oqmd = f.search("mdf.source_name:oqmd AND mdf.resource_type:dataset", advanced=True)[0] + nist_janaf = f.search("mdf.source_name:nist_janaf AND mdf.resource_type:dataset", + advanced=True)[0] # Fetch single dataset - f1 = forge.Forge() - res1 = f1.fetch_datasets_from_results(res01[0]) + res1 = f.fetch_datasets_from_results(res01[0]) assert res1[0] == oqmd + # Fetch dataset with results + info - f2 = forge.Forge() - res2 = f2.fetch_datasets_from_results(res02) + res2 = f.fetch_datasets_from_results(res02) assert res2[0] == oqmd + # Fetch multiple datasets - f3 = forge.Forge() rtemp = res01+res03 - res3 = f3.fetch_datasets_from_results(rtemp) + res3 = f.fetch_datasets_from_results(rtemp) assert len(res3) == 2 assert oqmd in res3 assert nist_janaf in res3 + # Fetch dataset from dataset - f4 = forge.Forge() - res4 = f4.fetch_datasets_from_results(res04) + res4 = f.fetch_datasets_from_results(res04) assert res4 == res04 + # Fetch entries from current query - f5 = forge.Forge() - f5.match_sources("nist_xps_db") - assert f5.fetch_datasets_from_results() == res04 + f.match_sources("nist_xps_db") + assert f.fetch_datasets_from_results() == res04 + # Fetch nothing - f6 = forge.Forge() unknown_entry = {"mdf": {"resource_type": "unknown"}} - assert f6.fetch_datasets_from_results(unknown_entry) == [] + assert f.fetch_datasets_from_results(unknown_entry) == [] def test_forge_aggregate(): # Test that aggregate uses the current query properly # And returns results # And respects the reset_query arg - f1 = forge.Forge() - f1.match_field("mdf.source_name", "nist_xps_db") - res1 = f1.aggregate(reset_query=False) + f = forge.Forge() + f.match_field("mdf.source_name", "nist_xps_db") + res1 = f.aggregate(reset_query=False) assert len(res1) > 10000 - res2 = f1.aggregate() + res2 = f.aggregate() assert all([r in res2 for r in res1]) and all([r in res1 for r in res2]) @@ -801,12 +782,14 @@ def test_forge_globus_download(): f.globus_download(example_result1) assert os.path.exists("./test_fetch.txt") os.remove("./test_fetch.txt") + # With dest and preserve_dir dest_path = os.path.expanduser("~/mdf") f.globus_download(example_result1, dest=dest_path, preserve_dir=True) assert os.path.exists(os.path.join(dest_path, "test", "test_fetch.txt")) os.remove(os.path.join(dest_path, "test", "test_fetch.txt")) os.rmdir(os.path.join(dest_path, "test")) + # With multiple files f.globus_download(example_result2, dest=dest_path) assert os.path.exists(os.path.join(dest_path, "test_fetch.txt")) @@ -816,18 +799,20 @@ def test_forge_globus_download(): def test_forge_http_stream(capsys): - f1 = forge.Forge() + f = forge.Forge() # Simple case - res1 = f1.http_stream(example_result1) + res1 = f.http_stream(example_result1) assert isinstance(res1, types.GeneratorType) assert next(res1) == "This is a test document for Forge testing. Please do not remove.\n" + # With multiple files - res2 = f1.http_stream((example_result2, {"info": {}})) + res2 = f.http_stream((example_result2, {"info": {}})) assert isinstance(res2, types.GeneratorType) assert next(res2) == "This is a test document for Forge testing. Please do not remove.\n" assert next(res2) == "This is a second test document for Forge testing. Please do not remove.\n" + # Too many results - res3 = f1.http_stream(list(range(10001))) + res3 = f.http_stream(list(range(10001))) assert next(res3)["success"] is False out, err = capsys.readouterr() assert ("Too many results supplied. Use globus_download() for " @@ -836,8 +821,7 @@ def test_forge_http_stream(capsys): next(res3) # "Missing" files - f2 = forge.Forge() - assert next(f2.http_stream(example_result_missing)) is None + assert next(f.http_stream(example_result_missing)) is None out, err = capsys.readouterr() assert not os.path.exists("./missing.txt") assert ("Error 404 when attempting to access " @@ -850,6 +834,7 @@ def test_forge_http_return(): res1 = f.http_return(example_result1) assert isinstance(res1, list) assert res1 == ["This is a test document for Forge testing. Please do not remove.\n"] + # With multiple files res2 = f.http_return(example_result2) assert isinstance(res2, list) @@ -858,17 +843,17 @@ def test_forge_http_return(): def test_forge_chaining(): - f1 = forge.Forge() - f1.match_field("source_name", "cip") - f1.match_field("elements", "Al") - res1 = f1.search() - res2 = forge.Forge().match_field("source_name", "cip").match_field("elements", "Al").search() + f = forge.Forge() + f.match_field("source_name", "cip") + f.match_field("elements", "Al") + res1 = f.search() + res2 = f.match_field("source_name", "cip").match_field("elements", "Al").search() assert all([r in res2 for r in res1]) and all([r in res1 for r in res2]) def test_forge_show_fields(): - f1 = forge.Forge() - res1 = f1.show_fields() + f = forge.Forge() + res1 = f.show_fields() assert "mdf" in res1.keys() - res2 = f1.show_fields("mdf") + res2 = f.show_fields("mdf") assert "mdf.mdf_id" in res2.keys() From 98bf8ffa7f7ea98bfcc05facbd0e71d834885b56 Mon Sep 17 00:00:00 2001 From: jgaff Date: Tue, 12 Dec 2017 15:13:30 -0600 Subject: [PATCH 17/22] aggregate default to search, bugfix Forge.aggregate() will default to Forge.search() when the total number of results is under 10,000, which will increase the speed of searches. Bugfix in globus_download. Remove long test validation, replace with similar (but faster) result validation. --- mdf_forge/forge.py | 8 ++++++-- tests/test_forge.py | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/mdf_forge/forge.py b/mdf_forge/forge.py index ac9b966..2369eb4 100644 --- a/mdf_forge/forge.py +++ b/mdf_forge/forge.py @@ -862,9 +862,9 @@ def globus_download(self, results, dest=".", dest_ep=None, preserve_dir=False, polling_interval=10): if verbose: print_("Transferring...") - for event in self.__transfer_client.task_event_list(res["task_id"]): + for event in self.__transfer_client.task_event_list(result["task_id"]): if event["is_error"]: - self.__transfer_client.cancel_task(res["task_id"]) + self.__transfer_client.cancel_task(result["task_id"]) raise globus_sdk.GlobusError("Error: " + event["description"]) submissions.append(result["task_id"]) @@ -1188,6 +1188,10 @@ def aggregate(self, q=None, scroll_size=SEARCH_LIMIT): # Get the total number of records total = self.search(q, limit=0, advanced=True, info=True)[1]["total_query_matches"] + # If aggregate is unnecessary, use Search automatically instead + if total <= SEARCH_LIMIT: + return self.search(q, limit=SEARCH_LIMIT, advanced=True) + # Scroll until all results are found output = [] diff --git a/tests/test_forge.py b/tests/test_forge.py index baf9f3a..5b0dfca 100644 --- a/tests/test_forge.py +++ b/tests/test_forge.py @@ -715,8 +715,10 @@ def test_forge_aggregate(): f.match_field("mdf.source_name", "nist_xps_db") res1 = f.aggregate(reset_query=False) assert len(res1) > 10000 + assert check_field(res1, "mdf.source_name", "nist_xps_db") == 0 res2 = f.aggregate() - assert all([r in res2 for r in res1]) and all([r in res1 for r in res2]) + assert len(res2) == len(res1) + assert check_field(res2, "mdf.source_name", "nist_xps_db") == 0 def test_forge_reset_query(): From 0b518ced687dde5c8f62e1932601c128b246b871 Mon Sep 17 00:00:00 2001 From: jgaff Date: Fri, 5 Jan 2018 11:15:07 -0600 Subject: [PATCH 18/22] Update SearchClient, start anonymous login Toolbox has updated to the latest globus_sdk, where the SearchClient is now maintained. The SearchClient has also been updated, and both Forge and Query have been updated to match the new behavior. The only user-facing change is that the search index can be specified when searching, instead of only when instantiating a Forge object. Also moving the examples into the docs dir, to keep the Jupyte notebooks all in a separate space. --- .travis.yml | 2 +- README.md | 2 +- .../examples}/Example Aggregations.ipynb | 0 ...xample Analysis - Fe-Cr-Al Oxidation.ipynb | 0 .../Example Machine Learning - OQMD.ipynb | 0 .../Example Statistics - MDF Datasets.ipynb | 0 {examples => docs/examples}/requirements.txt | 0 mdf_forge/forge.py | 127 +++++++++++++----- setup.py | 4 +- tests/test_forge.py | 102 ++++++++------ 10 files changed, 157 insertions(+), 80 deletions(-) rename {examples => docs/examples}/Example Aggregations.ipynb (100%) rename {examples => docs/examples}/Example Analysis - Fe-Cr-Al Oxidation.ipynb (100%) rename {examples => docs/examples}/Example Machine Learning - OQMD.ipynb (100%) rename {examples => docs/examples}/Example Statistics - MDF Datasets.ipynb (100%) rename {examples => docs/examples}/requirements.txt (100%) diff --git a/.travis.yml b/.travis.yml index 881cdc0..fd6bba6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: python python: - '2.7' -- '3.3' +#- '3.3' # Bug in TravisCI prevents 3.3 tests running (old pip in path) - '3.4' - '3.5' - '3.6' diff --git a/README.md b/README.md index b4b315c..17c8cae 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ r = mdf.search("materials commons") r_2 = mdf.search_by_elements(elements=["Al","Cu"], sources=["oqmd"]) ``` -More examples are available in the examples directory. +More examples are available in the docs/examples directory. # Documentation diff --git a/examples/Example Aggregations.ipynb b/docs/examples/Example Aggregations.ipynb similarity index 100% rename from examples/Example Aggregations.ipynb rename to docs/examples/Example Aggregations.ipynb diff --git a/examples/Example Analysis - Fe-Cr-Al Oxidation.ipynb b/docs/examples/Example Analysis - Fe-Cr-Al Oxidation.ipynb similarity index 100% rename from examples/Example Analysis - Fe-Cr-Al Oxidation.ipynb rename to docs/examples/Example Analysis - Fe-Cr-Al Oxidation.ipynb diff --git a/examples/Example Machine Learning - OQMD.ipynb b/docs/examples/Example Machine Learning - OQMD.ipynb similarity index 100% rename from examples/Example Machine Learning - OQMD.ipynb rename to docs/examples/Example Machine Learning - OQMD.ipynb diff --git a/examples/Example Statistics - MDF Datasets.ipynb b/docs/examples/Example Statistics - MDF Datasets.ipynb similarity index 100% rename from examples/Example Statistics - MDF Datasets.ipynb rename to docs/examples/Example Statistics - MDF Datasets.ipynb diff --git a/examples/requirements.txt b/docs/examples/requirements.txt similarity index 100% rename from examples/requirements.txt rename to docs/examples/requirements.txt diff --git a/mdf_forge/forge.py b/mdf_forge/forge.py index 2369eb4..29e8ad1 100644 --- a/mdf_forge/forge.py +++ b/mdf_forge/forge.py @@ -23,35 +23,44 @@ class Forge: Public Variables: local_ep is the endpoint ID of the local Globus Connect Personal endpoint. - + index is the Globus Search index to be used. """ __default_index = "mdf" __services = ["mdf", "transfer", "search"] __app_name = "MDF_Forge" - def __init__(self, index=__default_index, local_ep=None, **kwargs): + def __init__(self, index=__default_index, local_ep=None, anonymous=False, **kwargs): """Initialize the Forge instance. Arguments: index (str): The Globus Search index to search on. Default "mdf". local_ep (str): The endpoint ID of the local Globus Connect Personal endpoint. If not provided, may be autodetected as possible. + anonymous (bool): If True, will not authenticate with Globus Auth. + If False, will require authentication. + Please note that authentication is required for some Forge + functionality, including using Globus Transfer. Keyword Arguments: services (list of str): The services to authenticate for. Advanced users only. """ - self.__index = index + self.__anonymous = anonymous + self.index = index self.local_ep = local_ep - self.__services = kwargs.get('services', self.__services) - clients = toolbox.login(credentials={ - "app_name": self.__app_name, - "services": self.__services, - "index": self.__index}) - self.__search_client = clients["search"] - self.__transfer_client = clients["transfer"] - self.__mdf_authorizer = clients["mdf"] + services = kwargs.get('services', self.__services) + + if self.__anonymous: + clients = toolbox.anonymous_login(services) + else: + clients = toolbox.login(credentials={ + "app_name": self.__app_name, + "services": services, + "index": self.index}) + self.__search_client = clients.get("search") + self.__transfer_client = clients.get("transfer") + self.__mdf_authorizer = clients.get("mdf") self.__query = Query(self.__search_client) @@ -121,13 +130,14 @@ def exclude_field(self, field, value, new_group=False): self.__query.negate().field(str(field), str(value)) return self - def search(self, q=None, advanced=False, limit=SEARCH_LIMIT, info=False, + def search(self, q=None, index=None, advanced=False, limit=SEARCH_LIMIT, info=False, reset_query=True): """Execute a search and return the results. Arguments: q (str): The query to execute. Defaults to the current query, if any. There must be some query to execute. + index (str): The Globus Search index to search on. Defaults to the current index. advanced (bool): If True, will submit query in "advanced" mode to enable field matches. If False, only basic fulltext term matches will be supported. Default False. @@ -147,12 +157,14 @@ def search(self, q=None, advanced=False, limit=SEARCH_LIMIT, info=False, list (if info=False): The results. tuple (if info=True): The results, and a dictionary of query information. """ - res = self.__query.search(q=q, advanced=advanced, limit=limit, info=info) + if not index: + index = self.index + res = self.__query.search(q=q, index=index, advanced=advanced, limit=limit, info=info) if reset_query: self.reset_query() return res - def aggregate(self, q=None, scroll_size=SEARCH_LIMIT, reset_query=True): + def aggregate(self, q=None, index=None, scroll_size=SEARCH_LIMIT, reset_query=True): """Perform an advanced query, and return all matching results. Will automatically preform multiple queries in order to retrieve all results. @@ -161,6 +173,7 @@ def aggregate(self, q=None, scroll_size=SEARCH_LIMIT, reset_query=True): Arguments: q (str): The query to execute. Defaults to the current query, if any. There must be some query to execute. + index (str): The Globus Search index to search on. Defaults to the current index. scroll_size (int): Minimum number of records returned per query reset_query (bool): If True, will destroy the query after execution and start a fresh one. If False, will keep the current query alive. @@ -169,22 +182,27 @@ def aggregate(self, q=None, scroll_size=SEARCH_LIMIT, reset_query=True): Returns: list of dict: All matching records """ - res = self.__query.aggregate(q=q, scroll_size=scroll_size) + if not index: + index = self.index + res = self.__query.aggregate(q=q, index=index, scroll_size=scroll_size) if reset_query: self.reset_query() return res - def show_fields(self, block=None): + def show_fields(self, block=None, index=None): """Retrieve and return the mapping for the given metadata block. Arguments: block (str): The top-level field to fetch the mapping for. Default None, which lists just the blocks. + index (str): The Globus Search index to map. Defaults to the current index. Returns: dict: A set of field:datatype pairs. """ - mapping = self.__query.mapping() + if not index: + index = self.index + mapping = self.__query.mapping(index=index) if not block: blocks = set() for key in mapping.keys(): @@ -520,7 +538,8 @@ def match_resource_types(self, types): # * Premade searches # *********************************************** - def search_by_elements(self, elements, sources=[], limit=None, match_all=True, info=False): + def search_by_elements(self, elements, sources=[], index=None, limit=None, + match_all=True, info=False): """Execute a search for the given elements in the given sources. search_by_elements([x], [y]) is equivalent to match_elements([x]).match_sources([y]).search() @@ -529,6 +548,7 @@ def search_by_elements(self, elements, sources=[], limit=None, match_all=True, i Arguments: elements (list of str): The elements to match. Default []. sources (list of str): The sources to match. Default []. + index (str): The Globus Search index to search on. Defaults to the current index. limit (int): The maximum number of results to return. The max for this argument is the SEARCH_LIMIT imposed by Globus Search. match_all (bool): If True, will add elements with AND. @@ -545,14 +565,15 @@ def search_by_elements(self, elements, sources=[], limit=None, match_all=True, i """ return (self.match_elements(elements, match_all=match_all) .match_sources(sources) - .search(limit=limit, info=info)) + .search(index=index, limit=limit, info=info)) - def search_by_titles(self, titles, limit=None, info=False): + def search_by_titles(self, titles, index=None, limit=None, info=False): """Execute a search for the given titles. search_by_titles([x]) is equivalent to match_titles([x]).search() Arguments: titles (list of str): The titles to match. Default []. + index (str): The Globus Search index to search on. Defaults to the current index. limit (int): The maximum number of results to return. The max for this argument is the SEARCH_LIMIT imposed by Globus Search. info (bool): If False, search will return a list of the results. @@ -564,14 +585,15 @@ def search_by_titles(self, titles, limit=None, info=False): list (if info=False): The results. tuple (if info=True): The results, and a dictionary of query information. """ - return self.match_titles(titles).search(limit=limit, info=info) + return self.match_titles(titles).search(index=index, limit=limit, info=info) - def search_by_tags(self, tags, limit=None, match_all=True, info=False): + def search_by_tags(self, tags, index=None, limit=None, match_all=True, info=False): """Execute a search for the given tag. search_by_tags([x]) is equivalent to match_tags([x]).search() Arguments: tags (list of str): The tags to match. Default []. + index (str): The Globus Search index to search on. Defaults to the current index. limit (int): The maximum number of results to return. The max for this argument is the SEARCH_LIMIT imposed by Globus Search. match_all (bool): If True, will add elements with AND. @@ -586,20 +608,23 @@ def search_by_tags(self, tags, limit=None, match_all=True, info=False): list (if info=False): The results. tuple (if info=True): The results, and a dictionary of query information. """ - return self.match_tags(tags, match_all=match_all).search(limit=limit, info=info) + return self.match_tags(tags, match_all=match_all).search(index=index, + limit=limit, + info=info) - def aggregate_source(self, sources): + def aggregate_source(self, sources, index=None): """Aggregate all records from a given source. There is no limit to the number of results returned. Please beware of aggregating very large datasets. Arguments: sources (str or list of str): The source to aggregate. + index (str): The Globus Search index to search on. Defaults to the current index. Returns: list of dict: All of the records from the source. """ - return self.match_sources(sources).aggregate() + return self.match_sources(sources).aggregate(index=index) def fetch_datasets_from_results(self, entries=None, query=None, reset_query=True): """Retrieve the dataset entries for given records. @@ -956,7 +981,7 @@ class Query: Queries may end up wrapped in parentheses, which has no direct effect on the search. Adding terms must be chained with .and() or .or(). Terms will not have spaces in between otherwise, and it is desirable to be explicit about - which terms are required. + which terms are required. """ def __init__(self, search_client, q=None, limit=None, advanced=False): """Initialize the Query instance. @@ -976,6 +1001,19 @@ def __init__(self, search_client, q=None, limit=None, advanced=False): # initialized is True if something has been added to the query # __init__(), term(), and field() can change this value to True self.initialized = not self.query == "(" + # Search index UUIDs, which are required instead of names + self.__index_uuids = { + "mdf": "d6cc98c3-ff53-4ee2-b22b-c6f945c0d30c", + "mdf-test": "c082b745-32ac-4ad2-9cde-92393f6e505c", + "dlhub": "847c9105-18a0-4ffb-8a71-03dd76dfcc9d", + "dlhub-test": "5c89e0a9-00e5-4171-b415-814fe4d0b8af" + } + + def __translate_index(self, index): + """Translate a known Globus Search index into the index UUID. + The UUID is now the only way to access indices. + """ + return self.__index_uuids.get(index.strip().lower(), index) def __clean_query_string(self, q): """Clean up a query string. @@ -1113,12 +1151,13 @@ def negate(self): self.operator("NOT") return self - def search(self, q=None, advanced=None, limit=SEARCH_LIMIT, info=False): + def search(self, q=None, index=None, advanced=None, limit=SEARCH_LIMIT, info=False): """Execute a search and return the results. Arguments: q (str): The query to execute. Defaults to the current query, if any. There must be some query to execute. + index (str): The Globus Search index to search on. Required. advanced (bool): If True, will submit query in "advanced" mode to enable field matches. If False, only basic fulltext term matches will be supported. Default False. @@ -1140,6 +1179,11 @@ def search(self, q=None, advanced=None, limit=SEARCH_LIMIT, info=False): if not q.strip("()"): print_("Error: No query") return ([], {"error": "No query"}) if info else [] + if index is None: + print_("Error: No index specified") + return ([], {"error": "No index"}) if info else [] + else: + uuid_index = self.__translate_index(index) if advanced is None or self.advanced: advanced = self.advanced if limit is None: @@ -1156,13 +1200,15 @@ def search(self, q=None, advanced=None, limit=SEARCH_LIMIT, info=False): "limit": limit, "offset": 0 } - res = toolbox.gmeta_pop(self.__search_client.structured_search(qu), info=info) + res = toolbox.gmeta_pop(self.__search_client.post_search(uuid_index, qu), info=info) # Add additional info if info: res[1]["query"] = qu + res[1]["index"] = index + res[1]["index_uuid"] = uuid_index return res - def aggregate(self, q=None, scroll_size=SEARCH_LIMIT): + def aggregate(self, q=None, index=None, scroll_size=SEARCH_LIMIT): """Gather all results that match a specific query Note that all aggregate queries run in advanced mode. @@ -1180,17 +1226,21 @@ def aggregate(self, q=None, scroll_size=SEARCH_LIMIT): if not q.strip("()"): print_("Error: No query") return [] + if index is None: + print_("Error: No index specified") + return [] q = self.__clean_query_string(q) # TODO: Remove record restriction (all entries require scroll_id) q += " AND mdf.resource_type:record" # Get the total number of records - total = self.search(q, limit=0, advanced=True, info=True)[1]["total_query_matches"] + total = self.search(q, index=index, limit=0, advanced=True, + info=True)[1]["total_query_matches"] # If aggregate is unnecessary, use Search automatically instead if total <= SEARCH_LIMIT: - return self.search(q, limit=SEARCH_LIMIT, advanced=True) + return self.search(q, index=index, limit=SEARCH_LIMIT, advanced=True) # Scroll until all results are found output = [] @@ -1209,7 +1259,7 @@ def aggregate(self, q=None, scroll_size=SEARCH_LIMIT): while True: query = "(" + q + ') AND (mdf.scroll_id:>=%d AND mdf.scroll_id:<%d)' % ( scroll_pos, scroll_pos+scroll_width) - results, info = self.search(query, advanced=True, info=True) + results, info = self.search(query, index=index, advanced=True, info=True) # Check to make sure that all the matching records were returned if info["total_query_matches"] <= len(results): @@ -1228,10 +1278,17 @@ def aggregate(self, q=None, scroll_size=SEARCH_LIMIT): return output - def mapping(self): - """Fetch the mapping for the current index. + def mapping(self, index): + """Fetch the mapping for the specified index. + + Arguments: + index (str): The index to map. Returns: dict: The full mapping for the index. """ - return self.__search_client.mapping()["mappings"] + return (self.__search_client.get( + # TODO: Re-enable when Search handles index UUIDs + #"/unstable/index/{}/mapping".format(self.__translate_index(index))) + "/unstable/index/{}/mapping".format(index)) + ["mappings"]) diff --git a/setup.py b/setup.py index 5a56f47..ed0d029 100644 --- a/setup.py +++ b/setup.py @@ -10,8 +10,8 @@ "Forge allows users to perform simple queries and " "facilitiates moving and synthesizing results."), install_requires=[ - "mdf-toolbox>=0.1.1", - "globus-sdk>=1.2.1", + "mdf-toolbox>=0.1.4", + "globus-sdk>=1.4.1", "requests>=2.18.4", "tqdm>=4.19.4", "six>=1.10.0" diff --git a/tests/test_forge.py b/tests/test_forge.py index 5b0dfca..b2486fb 100644 --- a/tests/test_forge.py +++ b/tests/test_forge.py @@ -2,14 +2,14 @@ import types import pytest import globus_sdk +from globus_sdk.exc import SearchAPIError from mdf_forge import forge from mdf_toolbox import toolbox # Manually logging in for Query testing query_search_client = toolbox.login(credentials={"app_name": "MDF_Forge", - "services": ["search"], - "index": "mdf"})["search"] + "services": ["search"]})["search"] def test_query_init(): @@ -77,7 +77,7 @@ def test_query_operator(capsys): # Add bad operator assert q.operator("FOO") == q out, err = capsys.readouterr() - assert "Error: 'FOO' is not a valid operator" + assert "Error: 'FOO' is not a valid operator" in out assert q.query == "(" # Test operator cleaning q.operator(" and ") @@ -120,29 +120,48 @@ def test_query_or_join(capsys): def test_query_search(capsys): # Error on no query q = forge.Query(query_search_client) - assert q.search() == [] + assert q.search(index="mdf") == [] out, err = capsys.readouterr() assert "Error: No query" in out assert q.search(info=True) == ([], {"error": "No query"}) + # Error on no index + assert q.search(q="abc") == [] + out, err = capsys.readouterr() + assert "Error: No index specified" in out + assert q.search(q="abc", info=True) == ([], {"error": "No index"}) + # Return info if requested - res2 = q.search(q="Al", info=False) + res2 = q.search(q="Al", index="mdf", info=False) assert isinstance(res2, list) assert isinstance(res2[0], dict) - res3 = q.search(q="Al", info=True) + res3 = q.search(q="Al", index="mdf", info=True) assert isinstance(res3, tuple) assert isinstance(res3[0], list) assert isinstance(res3[0][0], dict) assert isinstance(res3[1], dict) # Check limit - res4 = q.search("oqmd", limit=3) + res4 = q.search("oqmd", index="mdf", limit=3) assert len(res4) == 3 # Check limit correction - res5 = q.search("nist_xps_db", limit=20000) + res5 = q.search("nist_xps_db", index="mdf", limit=20000) assert len(res5) == 10000 + # Test index translation + # mdf = d6cc98c3-ff53-4ee2-b22b-c6f945c0d30c + res6 = q.search(q="data", index="mdf", limit=1, info=True) + assert len(res6[0]) == 1 + assert res6[1]["index"] == "mdf" + assert res6[1]["index_uuid"] == "d6cc98c3-ff53-4ee2-b22b-c6f945c0d30c" + res7 = q.search(q="data", index="d6cc98c3-ff53-4ee2-b22b-c6f945c0d30c", limit=1, info=True) + assert len(res7[0]) == 1 + assert res7[1]["index"] == "d6cc98c3-ff53-4ee2-b22b-c6f945c0d30c" + assert res7[1]["index_uuid"] == "d6cc98c3-ff53-4ee2-b22b-c6f945c0d30c" + with pytest.raises(SearchAPIError): + q.search(q="data", index="invalid", limit=1, info=True) + def test_query_aggregate(capsys): q = forge.Query(query_search_client) @@ -152,12 +171,13 @@ def test_query_aggregate(capsys): assert "Error: No query" in out # Basic aggregation - res1 = q.aggregate("mdf.source_name:nist_xps_db") + res1 = q.aggregate("mdf.source_name:nist_xps_db", index="mdf") assert len(res1) > 10000 assert isinstance(res1[0], dict) # Multi-dataset aggregation - res2 = q.aggregate("(mdf.source_name:nist_xps_db OR mdf.source_name:nist_janaf)") + res2 = q.aggregate("(mdf.source_name:nist_xps_db OR mdf.source_name:nist_janaf)", + index="mdf") assert len(res2) > 10000 assert len(res2) > len(res1) @@ -167,12 +187,12 @@ def test_query_chaining(): q.field("source_name", "cip") q.and_join() q.field("elements", "Al") - res1 = q.search(limit=10000) + res1 = q.search(limit=10000, index="mdf") res2 = (forge.Query(query_search_client) .field("source_name", "cip") .and_join() .field("elements", "Al") - .search(limit=10000)) + .search(limit=10000, index="mdf")) assert all([r in res2 for r in res1]) and all([r in res1 for r in res2]) @@ -204,8 +224,8 @@ def test_query_cleaning(): # Test properties def test_forge_properties(): - f = forge.Forge() - assert type(f.search_client) is toolbox.SearchClient + f = forge.Forge(index="mdf") + assert type(f.search_client) is globus_sdk.SearchClient assert type(f.transfer_client) is globus_sdk.TransferClient assert type(f.mdf_authorizer) is globus_sdk.RefreshTokenAuthorizer @@ -335,7 +355,7 @@ def check_field(res, field, value): def test_forge_match_field(): - f = forge.Forge() + f = forge.Forge(index="mdf") # Basic usage f.match_field("mdf.source_name", "nist_janaf") res1 = f.search() @@ -350,7 +370,7 @@ def test_forge_match_field(): def test_forge_exclude_field(): - f = forge.Forge() + f = forge.Forge(index="mdf") # Basic usage f.exclude_field("mdf.elements", "Al") f.match_field("mdf.source_name", "core_mof") @@ -360,7 +380,7 @@ def test_forge_exclude_field(): def test_forge_match_range(): # Single-value use - f = forge.Forge() + f = forge.Forge(index="mdf") f.match_range("mdf.elements", "Al", "Al") res1, info1 = f.search(info=True) assert check_field(res1, "mdf.elements", "Al") == 1 @@ -385,7 +405,7 @@ def test_forge_match_range(): def test_forge_exclude_range(): # Single-value use - f = forge.Forge() + f = forge.Forge(index="mdf") f.exclude_range("mdf.elements", "Am", "*") f.exclude_range("mdf.elements", "*", "Ak") res1, info1 = f.search(info=True) @@ -407,7 +427,7 @@ def test_forge_exclude_range(): def test_forge_exclusive_match(): - f = forge.Forge() + f = forge.Forge(index="mdf") f.exclusive_match("mdf.elements", "Al") res1 = f.search() assert check_field(res1, "mdf.elements", "Al") == 0 @@ -421,7 +441,7 @@ def test_forge_exclusive_match(): def test_forge_match_sources(): - f = forge.Forge() + f = forge.Forge(index="mdf") # One source f.match_sources("nist_janaf") res1 = f.search() @@ -442,7 +462,7 @@ def test_forge_match_sources(): def test_forge_match_ids(): # Get a couple IDs - f = forge.Forge() + f = forge.Forge(index="mdf") res0 = f.search("mdf.source_name:nist_janaf", advanced=True, limit=2) id1 = res0[0]["mdf"]["mdf_id"] id2 = res0[1]["mdf"]["mdf_id"] @@ -466,7 +486,7 @@ def test_forge_match_ids(): def test_forge_match_elements(): - f = forge.Forge() + f = forge.Forge(index="mdf") # One element f.match_elements("Al") res1 = f.search() @@ -486,7 +506,7 @@ def test_forge_match_elements(): def test_forge_match_titles(): # One title - f = forge.Forge() + f = forge.Forge(index="mdf") titles1 = '"OQMD - Na1Y2Zr1"' res1 = f.match_titles(titles1).search() assert res1 != [] @@ -504,7 +524,7 @@ def test_forge_match_titles(): def test_forge_match_tags(): # Get one tag - f = forge.Forge() + f = forge.Forge(index="mdf") res0 = f.search("mdf.source_name:trinkle_elastic_fe_bcc", advanced=True, limit=1) tags1 = res0[0]["mdf"]["tags"][0] @@ -534,7 +554,7 @@ def test_forge_match_tags(): def test_forge_match_years(capfd): # One year of data/results - f = forge.Forge() + f = forge.Forge(index="mdf") years1 = "2015" res1 = f.match_years(years1).search(limit=10) assert res1 != [] @@ -566,7 +586,7 @@ def test_forge_match_years(capfd): def test_forge_match_resource_types(): - f = forge.Forge() + f = forge.Forge(index="mdf") # Test one type f.match_resource_types("record") res1 = f.search(limit=10) @@ -585,7 +605,7 @@ def test_forge_match_resource_types(): def test_forge_search(capsys): # Error on no query - f = forge.Forge() + f = forge.Forge(index="mdf") assert f.search() == [] out, err = capsys.readouterr() assert "Error: No query" in out @@ -613,7 +633,7 @@ def test_forge_search(capsys): def test_forge_search_by_elements(): - f = forge.Forge() + f = forge.Forge(index="mdf") elements = ["Cu", "Al"] sources = ["oqmd", "nist_xps_db"] res1, info1 = f.match_sources(sources).match_elements(elements).search(limit=10000, info=True) @@ -624,7 +644,7 @@ def test_forge_search_by_elements(): def test_forge_search_by_titles(): - f = forge.Forge() + f = forge.Forge(index="mdf") titles1 = ["\"AMCS - Tungsten\""] res1 = f.search_by_titles(titles1) assert check_field(res1, "mdf.title", "AMCS - Tungsten") == 0 @@ -635,7 +655,7 @@ def test_forge_search_by_titles(): def test_forge_search_by_tags(): - f = forge.Forge() + f = forge.Forge(index="mdf") tags1 = "DFT" res1 = f.search_by_tags(tags1) assert check_field(res1, "mdf.tags", "DFT") == 2 @@ -653,7 +673,7 @@ def test_forge_search_by_tags(): def test_forge_aggregate_source(): # Test limit - f = forge.Forge() + f = forge.Forge(index="mdf") res1 = f.aggregate_source("amcs") assert isinstance(res1, list) assert len(res1) > 10000 @@ -662,7 +682,7 @@ def test_forge_aggregate_source(): def test_forge_fetch_datasets_from_results(): # Get some results - f = forge.Forge() + f = forge.Forge(index="mdf") # Record from OQMD res01 = f.search("mdf.source_name:oqmd AND mdf.resource_type:record", advanced=True, limit=1) # Record from OQMD with info @@ -711,7 +731,7 @@ def test_forge_aggregate(): # Test that aggregate uses the current query properly # And returns results # And respects the reset_query arg - f = forge.Forge() + f = forge.Forge(index="mdf") f.match_field("mdf.source_name", "nist_xps_db") res1 = f.aggregate(reset_query=False) assert len(res1) > 10000 @@ -722,7 +742,7 @@ def test_forge_aggregate(): def test_forge_reset_query(): - f = forge.Forge() + f = forge.Forge(index="mdf") # Term will return results f.match_field("elements", "Al") f.reset_query() @@ -731,14 +751,14 @@ def test_forge_reset_query(): def test_forge_current_query(): - f = forge.Forge() + f = forge.Forge(index="mdf") # Query.clean_query() is already tested, just need to check basic functionality f.match_field("field", "value") assert f.current_query() == "(field:value)" def test_forge_http_download(capsys): - f = forge.Forge() + f = forge.Forge(index="mdf") # Simple case f.http_download(example_result1) assert os.path.exists("./test_fetch.txt") @@ -779,7 +799,7 @@ def test_forge_http_download(capsys): @pytest.mark.xfail(reason="Test relies on get_local_ep() which can require user input.") def test_forge_globus_download(): - f = forge.Forge() + f = forge.Forge(index="mdf") # Simple case f.globus_download(example_result1) assert os.path.exists("./test_fetch.txt") @@ -801,7 +821,7 @@ def test_forge_globus_download(): def test_forge_http_stream(capsys): - f = forge.Forge() + f = forge.Forge(index="mdf") # Simple case res1 = f.http_stream(example_result1) assert isinstance(res1, types.GeneratorType) @@ -831,7 +851,7 @@ def test_forge_http_stream(capsys): def test_forge_http_return(): - f = forge.Forge() + f = forge.Forge(index="mdf") # Simple case res1 = f.http_return(example_result1) assert isinstance(res1, list) @@ -845,7 +865,7 @@ def test_forge_http_return(): def test_forge_chaining(): - f = forge.Forge() + f = forge.Forge(index="mdf") f.match_field("source_name", "cip") f.match_field("elements", "Al") res1 = f.search() @@ -854,7 +874,7 @@ def test_forge_chaining(): def test_forge_show_fields(): - f = forge.Forge() + f = forge.Forge(index="mdf") res1 = f.show_fields() assert "mdf" in res1.keys() res2 = f.show_fields("mdf") From bc57792aafc1683f0497d33c9d96b24345e3432c Mon Sep 17 00:00:00 2001 From: jgaff Date: Fri, 5 Jan 2018 11:17:24 -0600 Subject: [PATCH 19/22] Typo fix --- mdf_forge/forge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mdf_forge/forge.py b/mdf_forge/forge.py index 29e8ad1..cdaa366 100644 --- a/mdf_forge/forge.py +++ b/mdf_forge/forge.py @@ -1289,6 +1289,6 @@ def mapping(self, index): """ return (self.__search_client.get( # TODO: Re-enable when Search handles index UUIDs - #"/unstable/index/{}/mapping".format(self.__translate_index(index))) + # "/unstable/index/{}/mapping".format(self.__translate_index(index))) "/unstable/index/{}/mapping".format(index)) ["mappings"]) From d44cdbfd1f78b88dd0acc2bffd530e760198c69f Mon Sep 17 00:00:00 2001 From: jgaff Date: Fri, 5 Jan 2018 13:03:07 -0600 Subject: [PATCH 20/22] Anonymous login option, remove http_return Forge can now be used anonymously by specifying anonymous=True when creating the object. This disables features that require authentication, which currently includes all data download options. HTTP downloads should have that requirement relaxed after upstream updates in the future. No other features should be impacted, although search results will be limited to public entries. Also, removing http_return because list(http_stream()) does the same thing and may be easier to understand. Remove http_return usage in tutorial and example. Finally, update credentials. --- ...xample Analysis - Fe-Cr-Al Oxidation.ipynb | 14 ++--- .../6 - Data Retrieval Functions.ipynb | 30 ----------- mdf_forge/forge.py | 51 ++++++++++-------- tests/test_forge.py | 42 ++++++++++----- travis.tar.enc | Bin 10256 -> 10256 bytes 5 files changed, 65 insertions(+), 72 deletions(-) diff --git a/docs/examples/Example Analysis - Fe-Cr-Al Oxidation.ipynb b/docs/examples/Example Analysis - Fe-Cr-Al Oxidation.ipynb index 17bb81a..755f9b8 100644 --- a/docs/examples/Example Analysis - Fe-Cr-Al Oxidation.ipynb +++ b/docs/examples/Example Analysis - Fe-Cr-Al Oxidation.ipynb @@ -123,7 +123,7 @@ " return tmp_d\n", " \n", "def get_fe_cr_al(r):\n", - " res = mdf.http_return(r)[0]\n", + " res = next(mdf.http_stream(r))\n", " \n", " params = format_get_cr_al_params(r['fe_cr_al_oxidation']) \n", " \n", @@ -188,9 +188,9 @@ "outputs": [ { "data": { - "image/png": "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\nLmXfvn18+/Zt2r8nsoe8IVFktb6+Pr1e4fF4xu3z+PFj7HZ72mMnk9CZM2d+2r5x48a0x52KnJyc\ntOL/N6UUJ0+eZNeuXZSXl/PgwQP6+vq4fPkyxcXFlJaWomka9+7d++tfB/w3kEQistrt27eZMWMG\nZ8+eHXPcVSnFsWPH0DQt7UQSi8Xo7u7G7XaPeyEMhUIEg0EGBgYmfdNccXExAC9fvky5XQWjifDf\nff4rS5YsAUZ3EQUFBRmPd+vWLaLRKIcOHQLg3bt3APr68vPzMZvNRKPRjH9L/P4kkYisNTIyQkdH\nB06nky1btozbp6Ojg87OTo4fP57WcxXBYJB4PM7u3btZs2bNmHaLxUIgEODOnTvU19cDYDKZiMVi\nY/rW1NRw/vx5rl69SmVlpb6rePXqFV1dXaxcuZJ58+ZNeW7T4fF48Pl8XLp0icrKSvLz8/U2pdSU\n6iRJHz58oLm5maamJkwmEwALFiwA4MWLF9hsNgYHB/n48aMeF382qZGIrNXT08P79+9/eksLRi+g\nX758mXI9I0nTNMxmM6tXrx63vaKigqKiopRjt3a7nXA4TEtLC8FgkM7OTmC0qO71eunt7WXv3r1c\nv36dCxcuUFdXh9Fo5MSJE2nNbToKCws5evQoT548YevWrVy8eJH29naam5upra1Na6ympiZcLhdu\nt1uPORwOioqKaGxs5MaNGzQ0NGAymaiqqvrFKxG/I9mRiKyVfE5kogthTU0NRqMRTdOmXM8Ih8M8\nffqUbdu2pZyy+lFtbS2tra08evQIh8PB4cOH+fTpE9euXePr168AbNq0CYDGxkYsFgs3b97k3Llz\n5OXl4XK5aGhooLy8fKpLzojX68VisdDa2sqVK1dQSlFQUJBWIrl//z6hUEhPkkkzZ87E7/dz6tQp\nfD4fJSUl+P1+zGbzr16G+A0ZlPrhkVghhBAiDXJrSwghREYkkQghhMiIJBIhhBAZkUQihBAiI5JI\nhBBCZEQSiRBCiIxIIhFCCJERSSRCCCEyIolECCFERiSRCCGEyMh3kX9L32RyPywAAAAASUVORK5C\nYII=\n", + "image/png": "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\nyuQAAAAASUVORK5CYII=\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -224,9 +224,9 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n/vx5bN68GWq1Wu5IQqusrMSdO3ewePFi+Pv7a7+A+5fY+fv7o7i4WOaUYnnc/29emqjf+LcrKI1G\ngzNnzqCyslI7VlVVhVOnTkGj0ciYTFxtbW1ISEjAsWPHsHHjRnh5eckdSXhubm7IyMjo9AUAkydP\nRkZGBlxcXGROKZbg4GAAQFFRUYfxw4cPQ6FQwN7eXo5Y1Ed4TltQ06dPx65duxATE4P4+HgYGRlh\n3bp1UCgUmDFjhtzxhPT5558jPz8f0dHRsLCwwK+//qrdplAoeJj8KVhbW2P06NFdbnN0dHzoNnq4\nV199FaNHj0ZiYiJu3rypfSNaUVER339hAIwkSZLkDkFPp6amBklJSThy5AgkSUJAQACWLl0KJycn\nuaMJSaPRoLq6usttsbGxWLBgQR8n0l9qtRrR0dFYuHCh3FGE1NjYiNTUVOzfvx8NDQ0YOnQo5s6d\ni7CwMLmjUS9jaRMREQmC57SJiIgEwdImIiISBEubiIhIECxtIiIiQbC0iYiIBMHSJiIiEgRLm+gB\nmZmZUKvVOH78uNxRepVGo8Hbb78tdwwi6gaWNhmU8vJyqNVqqNVqnDt3rkfnTkhIgFqtxrx58x66\nz/bt25GZmdmjj6srLl26hIiICHh7eyM0NBTZ2dmd9mltbcXUqVOxdu1aGRISiY+lTQYlKysLlpaW\nsLOzQ1ZWVo/N29jYiAMHDsDZ2RlFRUW4du1al/tlZGT06OM+i/z8fGzZsqVH5mptbUVsbCzq6uqw\nePFiuLu7Y8mSJR0+Cha4//ybmpoQExPTI49LZGhY2mQw2trakJ2djdDQUEyaNAl5eXloaWnpkbnz\n8vJw584dpKamAgBycnJ6ZN7eZG5uDnNz8x6Zq7y8HGVlZVi+fDlmzZqF1atXY9CgQSgoKNDuc+XK\nFaxfvx6JiYno169fjzwukaFhaZPBOHr0KGprazFlyhRMnToVt27dQmFhYY/MnZWVBV9fX3h6euLf\n//53l6tptVqN6upqnDhxQnuI/sFbf3799deYPHkyPDw84O/vj+joaFy8eLHDPlVVVVCr1UhLS8P3\n33+PsLAweHh44LXXXsPBgwcBABcvXkRUVBS8vb0REBCAdevW4cFPLH7YOe2ffvoJkZGR8PPzg6en\nJyZMmPDYG1HcuXMHADBgwAAAgJGREaytrdHc3KzdZ8WKFQgMDMS4ceMeORcRPRxLmwxGZmYmHBwc\n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\nRU5ErkJggg==\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -245,9 +245,9 @@ "outputs": [ { "data": { - "image/png": "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\nz19eDAfKmdByeFQEUSF4K/s8LfsOPdHIMOSVtlIKH374IXbu3ImxY8dizpw50HVv7e0SERF5yZBC\ne/v27bj88stRX1/ffdmUKVNw3333Ydq0aXkbjoiIiL40pHr81ltvxZQpU/DHP/4RH330EZ555hkE\ng0Hccsst+Z6PiIiIuvQb2k8//XSvl3/yySdYsmQJJk+ejEAggFmzZuH888/H+vXrCzIkERERDRDa\n99xzDy688EJs2rRpn8sPOuggPPXUU8hkMgCAtrY2vPTSS5gyZUrhJiUiIhrh+g3tl156CYceeij+\n4R/+AXfffXd3SP/oRz/CK6+8gmOPPRbz58/H/PnzsWHDBtx4441FGZqIiGgk6vdAtNLSUtx00034\nu7/7O9xyyy148cUXccstt2D+/Pn44x//iNWrV6O5uRljx47FySefjMrKymLNTURENOLYOnr88MMP\nx9NPP43HHnsM3/ve93DSSSfhxhtvxJlnnlno+YiIiKiL7aPHNU3Dt7/9bbz00kuwLAunn346Hn/8\ncSjFs0gQEREVw4Ch3dTUhKeeegqPPvooPvroI4wfPx733HMP7rjjDixfvhznnHMOPv3002LMSkRE\nNKL1W4+vWbMGV111FQAgGAwiFovhu9/9Lq655hqcfPLJmDdvHu69916cf/75OP/883H11VcjHA4X\nZXAiIqKRpt+V9h133IG5c+di7dq1WLt2La699lo88MAD2L17NwAgFArhuuuuw+9+9zt89NFHOP30\n04syNBER0UjUb2hv27YNCxYsQDAYBAB8/etfh5QS27dv3+d2M2bMwBNPPIErr7yycJMSERGNcP2G\ndl1dHVauXInm5mbE43E89thj8Pv9mDp1aq+3P+eccwoxIxEREWGA97RvuukmXHHFFTj55JMBALqu\n44YbbkBFRUUxZiMiIqK99Bvas2fPxv/+7//i/fffRzqdxqxZszBhwoRizUZERER7GfDkKqWlpZg/\nf34xZiEiIqJ+DOmrOYmIiKj4GNpEREQewdAmIiLyCIY2ERGRRzC0iYiIPMLWV3MWypo1a/DAAw9g\n06ZNiEajqKqqwpFHHomrrroKdXV13bfbsWMHbrvtNrzxxhtQSuGEE07Aj370I9TU1Dg4PRERUXHZ\nXmk/8MADOO+88/q8/vzzz8dDDz00qAePRqOYNWsWbr75Zixfvhz/9m//hvr6epx77rndp0pNJpP4\n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\n", + "image/png": "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\nh/r6eug6P6MhIiIqlCGF9s6dO3HppZdi48aN3ZdNmTIF99xzD6ZPn5634YiIiGifIdXjN998M6ZM\nmYKXXnoJH330EZ566ikEg0HcdNNN+Z6PiIiIsvoN7SeffLLXy9evX49ly5Zh8uTJCAQCmDNnDs49\n91xs2LChIEMSERHRAKF911134fzzz8emTZsOuHzatGl44oknkMlkAABtbW14/vnnMWXKlMJNSkRE\nNML1G9rPP/88jjjiCPzDP/wD7rzzzu6Q/tGPfoQXX3wRxx13HBYuXIiFCxfi008/xfXXX1+UoYmI\niEaifg9EKysrww033IC/+7u/w0033YTnnnsON910ExYuXIiXXnoJa9asQXNzM8aNG4eTTz4ZlZWV\nxZqbiIhoxHF09PhRRx2FJ598Eo888gi+973v4Utf+hKuv/56nHHGGYWej4iIiLIcHz2uaRq+/e1v\n4/nnn4dlWTjttNPw6KOPQilvnUGJiIjIqwYM7cbGRjzxxBN4+OGH8dFHH2HChAm46667cNttt2HF\nihU466yz8MknnxRjViIiohGt33r81VdfxRVXXAEACAaDiMVi+O53v4urrroKJ598MhYsWIC7774b\n5557Ls4991xceeWViEQiRRmciIhopOl3pX3bbbdh/vz5WLt2LdauXYurr74a9913H/bu3QsACIVC\nuOaaa/A///M/+Oijj3DaaacVZWgiIqKRqN/Q3rFjBxYtWoRgMAgA+NrXvgYpJXbu3HnA7WbNmoXH\nHnsMl19+eeEmJSIiGuH6De2ZM2di1apVaG5uRjwexyOPPAK/34+pU6f2evuzzjqrEDMSERERBvhM\n+4YbbsBll12Gk08+GQCg6zquu+46jBo1qhizERER0X76De25c+fif//3f/H+++8jnU5jzpw5qKmp\nKdZsREREtJ8BT65SVlaGhQsXFmMWIiIi6seQfjUnERERFR9Dm4iIyCMY2kRERB7B0CYiIvIIhjYR\nEZFHOPrVnIXy6quv4r777sOmTZvQ0dGBqqoqzJs3D1dccQVmzpzZfbtdu3bhlltuwV/+8hcopXDi\niSfiRz/6EWpra12cnoiIqLgcr7Tvu+8+nHPOOX1ef+655+KBBx4Y1IN3dHRgzpw5uPHGG7FixQr8\nv//3/7Bx40acffbZ3adKTSaT+Pa3v43NmzfjP/7jP3Drrbdi27ZtuOCCC5BIJAb1eERERF7meKX9\n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\nqA+OP9NesGABfvKTn+Cb3/wm5s2bh9raWmjagZk/2NOYEhERkXOOQ/vdd9/FtddeC9M08fbbb/d6\nG4Y2ERFR4TgO7X//939HKBTCf/7nf+KYY45BeXl5IeciIiKigzgO7U2bNuHKK6/El7/85ULOQ0RE\nRH1wfCBadfXgf7sKERER5Y/j0P7Wt76Fp556CqlUqpDzEBERUR8c1+NlZWUIhUL42te+hm984xuo\nra2Frus9bvf3f//3eR2QiIiIbI5D+9prr+3+37/85S97vY0QgqFNRERUII5D++GHHy7kHERERDQA\nx6F9/PHHF3IOIiIiGoDjA9EO1traitbW1nzOQkRERP1wvNIGgKamJtxxxx145ZVXEI/HAQDRaBSL\nFi3CVVddxd+xTUREVECOQ7uhoQHnnHMOWlpaUF9fj5kzZwIANm7ciGeeeQavv/46fve732HSpEkF\nG5aIiGgkcxzad955J+LxOB544AGccMIJB1y3du1aLF26FP/1X/+F22+/Pe9DEhER0SA+037jjTdw\n/vnn9whsAJg/fz7OO+88/OUvf8nrcERERLSP49Du7OzExIkT+7y+trYWXV1deRmKiIiIenIc2pMm\nTcJrr73W5/WvvfZav6FOREREw+M4tM844wysWbMG119/PbZv3959+fbt23HjjTfiT3/6E77xjW8U\nZEgiIiIaxIFo//zP/4xPPvkETz31FJ5++mn4/X4AgGEYUErh1FNPxSWXXFKwQYmIiEY6x6Ht8/lw\n11134dVXX8Xq1avR0NAAAJg8eTIWL16Mk046qWBDEhER0SBPrgIACxcuxMKFCwsxCxEREfXD8Wfa\nixcvxurVq/u8/pVXXsHixYvzMhQRERH15Di0d+7ciUQi0ef1yWQSjY2NeRlqpFFKobHTwMe709gR\nM2BK5fZIjuTm3taeQWvShFTemFsqhZaEib0JE0nDgvLI3AJAQBdujzFoQV1gfFRHxK9B89D4llQI\n6AIhXcArYyul0Jo0sbU9g70J77wnyblB1+N92bVrFyKRSL7ubsToykh8sCuJWFrCUkBHSiKWzqA6\nqqMqrEOI0txcxNIWNrYaMKSCzM7dmZYYF/Uh4h/y76EpKKUU4obE3oQFpQAFoCujkDQtlAd0+Es4\nEP2aHdil+nrojSaA6jIdo8N6d+iFfQKGBDJW6YaJVAoZ035dCyHg0wFds2c2pNvT9S1pSDTETGSy\n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\ntxTtSDscDlNaWioj8wbdrmkLIYQQVmWRecpCCCGEkKYthBBCWIQ0bSGEEMIipGkLIYQQFiFNWwgh\nhLAIadpCCCGERUjTFkIIISxCmrYQQghhEdK0hRBCCIuQa8LFQGlpKU899RSFhYUUFRXh9/vlurvt\nWLt2LWvWrKGwsJCDBw+SmZnJhRdeyM0330xiYqLZ5cWdjz76iKeeeort27dTWVmJz+dj9OjRzJo1\niyFDhphdniXccMMNbNy4kZkzZ/LrX//a7HLizqZNm7juuuuOWZ6UlMRnn31mQkXdlzTtGCguLuad\nd94hNzeXMWPGsHHjRrNLimvLly8nMzOTX//612RkZPD111/z+OOPs2nTJl599VVUVQKi5iorK8nN\nzeWaa67B5/NRUlLCU089xU9+8hP++te/0qdPH7NLjGurV69m69atZpdhCf/93//NyJEjm36X72+I\nPWnaMTB27Fg+/vhjAF577TVp2sexdOlSfD5f0+8FBQWkpqZy5513smnTJs4880wTq4s/l156KZde\nemmLZXl5eVx88cW8++67XH/99SZVFv8qKytZtGgRCxYsYO7cuWaXE/cGDx7MqFGjzC6jW5MhSwzI\nyLBjmjfsRo1H9/v37491OZaUmpoKyEjoeJYsWcIpp5xyzEGPEPFKRtrCEj799FMgcqQvWqdpGpqm\nUVJSwgMPPEBaWpo0o3Z89tlnrFq1ijfftOq32sfevHnzOHz4MMnJyYwfP565c+eSlZVldlndijRt\nEff279/Po48+yllnndXifJpo6aqrruI///kPAP379+f555+nZ8+eJlcVn4LBIHfffTfXX389gwYN\nMrucuJeUlMT111/P2LFjSUxM5Ouvv2bZsmV8+umnrFq1Sl5nMSRNW8S12tpabrnlFmw2G4sWLTK7\nnLi2ePFiampq2L17N8uXL2fGjBm8/PLL8imFVjz99NP4/X5uueUWs0uxhBEjRjBixIim3wsKChg7\ndixXXXUVK1askBn3MSQnW0Xc8vv9zJw5kz179vDMM8+QkZFhdklxbfDgweTn53PppZfy3HPPUVdX\nx5NPPml2WXGnpKSEpUuXMmfOHILBIFVVVVRVVQE0/a5pmslVxr/c3FwGDBhAYWGh2aV0KzLSFnEp\nFAoxe/ZsCgsLefbZZ8nJyTG7JEtJTk6mX79+fPfdd2aXEnd2795NIBDg9ttvP+a25cuXs3z5clat\n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\n/fcf8xEnwzC48847WblyZYebdmVlJevXr+f8889vtels2LCB1atXc+DAAdLT09vdVt++fQHYtm1b\ni8gbIgcdzdfpKgMHDgQio+PMzMyT3t6rr75KaWkps2fPBmD//v0ATY/P7XaTmppKaWnpSd+XEN2F\nNG3xvaZpGm+99RajRo1i6tSpra7z1ltvsWbNGu66664OfW559erVBINBfv7zn3PWWWcdc3u/fv14\n8803WbVqFTfddBMAXq+XysrKY9adOHEiDzzwAMuXL+eMM85oGi3v3LmTd999l9GjR+Pz+aKu7URc\neOGFLFmyhD/96U+cccYZuN3uptsMw4jqvHaj8vJyHnzwQRYtWoTX6wUgLS0NgG+//Zbc3FwOHjzI\noUOHmpYLIY5PzmmL77WPPvqIsrKyNmNxiDSrqqqqqM8/N1q5ciWpqakUFBS0evuIESPIzs5u8VGr\nvLw8vvnmGx577DFWr17NmjVrgMiEs+nTp7Nx40amTZvGihUrePTRR/nZz36G3W7nt7/9bYdqOxFZ\nWVnMmzePzZs3c/nll/P444/zl7/8hQcffJALLrigQ9tatGgRY8aM4fzzz29alp+fT3Z2NvPnz+el\nl15izpw5eL1efvjDH3byIxHi+0tG2uJ7rfFz2O01nYkTJ2K321m5cmXU55+/+eYbCgsLueKKK1rM\n9j7aBRdcwLPPPsuXX35Jfn4+t912G4cPH+b555+nuroagEsuuQSA+fPn069fP1555RUWL16My+Vi\nzJgxzJkzh+HDh0f7kE/K9OnT6devH88++yzPPPMMhmGQmZnZoab9j3/8gw0bNjQdkDRyOp0sXbqU\ne+65hyVLljBgwACWLl1KampqZz8MIb63FMM46jJJQgghhIhLEo8LIYQQFiFNWwghhLAIadpCCCGE\nRUjTFkIIISxCmrYQQghhEdK0hRBCCIuQpi2EEEJYhDRtIYQQwiKkaQshhBAW8f8B+UgbjvwC5BsA\nAAAASUVORK5CYII=\n", "text/plain": [ - "" + "" ] }, "metadata": {}, diff --git a/docs/tutorials/6 - Data Retrieval Functions.ipynb b/docs/tutorials/6 - Data Retrieval Functions.ipynb index 3ea71be..4910ab3 100644 --- a/docs/tutorials/6 - Data Retrieval Functions.ipynb +++ b/docs/tutorials/6 - Data Retrieval Functions.ipynb @@ -152,36 +152,6 @@ "next(raw_data)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### http_return\n", - "To fetch all the data all at once in one big list, use `http_return()`. This function is equivalent to `list(http_stream())`." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'CHEMBL1200572\\n SciTegic12231509382D\\n\\n 2 1 0 0 0 0 999 V2000\\n 4.8167 -4.8250 0.0000 Mg 0 0\\n 5.5292 -4.8250 0.0000 O 0 0\\n 2 1 2 0\\nM END\\n> \\nCHEMBL1200572\\n\\n'" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "res = mdf.search(\"MgO\", limit=1)\n", - "raw_data = mdf.http_return(res)\n", - "raw_data[0]" - ] - }, { "cell_type": "code", "execution_count": null, diff --git a/mdf_forge/forge.py b/mdf_forge/forge.py index cdaa366..9a642ba 100644 --- a/mdf_forge/forge.py +++ b/mdf_forge/forge.py @@ -692,7 +692,18 @@ def http_download(self, results, dest=".", preserve_dir=False, verbose=True): verbose (bool): If True, status and progress messages will be printed. If False, only error messages will be printed. Default True. + + Returns: + dict: success (bool): True if the operation succeeded. + False if it failed (implies message). + message (str): The error message. Not present when success is True. """ + if self.__anonymous: + print_("Error: Anonymous HTTP download not yet supported.") + return { + "success": False, + "message": "Anonymous HTTP download not yet supported." + } # If user submitted single result, make into list if isinstance(results, dict): results = [results] @@ -768,6 +779,9 @@ def http_download(self, results, dest=".", preserve_dir=False, verbose=True): # Write out the binary response content with open(local_path, 'wb') as output: output.write(response.content) + return { + "success": True + } def globus_download(self, results, dest=".", dest_ep=None, preserve_dir=False, wait_for_completion=True, verbose=True): @@ -787,13 +801,19 @@ def globus_download(self, results, dest=".", dest_ep=None, preserve_dir=False, wait_for_completion (bool): If True, will block until the transfer is finished. If False, will not block. Default True. - verbose (bool): If True, status and progress messages will be print_ed. - If False, only error messages will be print_ed. + verbose (bool): If True, status and progress messages will be printed. + If False, only error messages will be printed. Default True. Returns: list of str: task IDs of the Globus transfers """ + if self.__anonymous: + print_("Error: Anonymous Globus Transfer not supported.") + return { + "success": False, + "message": "Anonymous Globus Transfer not supported." + } dest = os.path.abspath(dest) # If results have info attached, remove it if type(results) is tuple: @@ -906,13 +926,19 @@ def http_stream(self, results, verbose=True): Arguments: results (dict): The records from which files should be fetched. This should be the return value of a search method. - verbose (bool): If True, status and progress messages will be print_ed. - If False, only error messages will be print_ed. + verbose (bool): If True, status and progress messages will be printed. + If False, only error messages will be printed. Default True. Yields: str: Text of each data file. """ + if self.__anonymous: + print_("Error: Anonymous HTTP download not yet supported.") + return { + "success": False, + "message": "Anonymous HTTP download not yet supported." + } # If results have info attached, remove it if type(results) is tuple: results = results[0] @@ -953,23 +979,6 @@ def http_stream(self, results, verbose=True): else: yield response.text - def http_return(self, results, verbose=True): - """Return data files from the provided results using HTTPS. - For more than HTTP_NUM_LIMIT (defined above) files, you should use globus_download(), - which uses Globus Transfer. - - Arguments: - results (dict): The records from which files should be fetched. - This should be the return value of a search method. - verbose (bool): If True, status and progress messages will be print_ed. - If False, only error messages will be print_ed. - Default True. - - Returns: - list of str: Text data of the data files. - """ - return list(self.http_stream(results, verbose=verbose)) - class Query: """The Query class is meant for internal Forge use. Users should not instantiate diff --git a/tests/test_forge.py b/tests/test_forge.py index b2486fb..d632bca 100644 --- a/tests/test_forge.py +++ b/tests/test_forge.py @@ -631,6 +631,10 @@ def test_forge_search(capsys): res6 = f.search() assert all([r in res6 for r in res5]) and all([r in res5 for r in res6]) + # Check default index + f2 = forge.Forge() + assert f2.search("data", limit=1, info=True)[1]["index"] == "mdf" + def test_forge_search_by_elements(): f = forge.Forge(index="mdf") @@ -850,20 +854,6 @@ def test_forge_http_stream(capsys): "'https://data.materialsdatafacility.org/test/missing.txt'") in out -def test_forge_http_return(): - f = forge.Forge(index="mdf") - # Simple case - res1 = f.http_return(example_result1) - assert isinstance(res1, list) - assert res1 == ["This is a test document for Forge testing. Please do not remove.\n"] - - # With multiple files - res2 = f.http_return(example_result2) - assert isinstance(res2, list) - assert res2 == ["This is a test document for Forge testing. Please do not remove.\n", - "This is a second test document for Forge testing. Please do not remove.\n"] - - def test_forge_chaining(): f = forge.Forge(index="mdf") f.match_field("source_name", "cip") @@ -879,3 +869,27 @@ def test_forge_show_fields(): assert "mdf" in res1.keys() res2 = f.show_fields("mdf") assert "mdf.mdf_id" in res2.keys() + + +def test_forge_anonymous(capsys): + f = forge.Forge(anonymous=True) + # Test search + assert len(f.search("mdf.source_name:oqmd", advanced=True, limit=300)) == 300 + + # Test aggregation + assert len(f.aggregate("mdf.source_name:nist_xps_db")) > 10000 + + # Error on auth-only functions + # http_download + assert f.http_download({})["success"] is False + out, err = capsys.readouterr() + assert "Error: Anonymous HTTP download not yet supported." in out + # globus_download + assert f.globus_download({})["success"] is False + out, err = capsys.readouterr() + assert "Error: Anonymous Globus Transfer not supported." in out + # http_stream + with pytest.raises(StopIteration): + next(f.http_stream({})) + out, err = capsys.readouterr() + assert "Error: Anonymous HTTP download not yet supported." in out diff --git a/travis.tar.enc b/travis.tar.enc index 2b0d613fcaf4211ebf126f9906a93ff9b62ad568..20ae82383bc4767136f4d9a5a118a75f6b823284 100644 GIT binary patch literal 10256 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 W5uC7XKE(f%zNYr??fe5g1d8~L5&R4Q literal 10256 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 From 39381ba89890ca9132ddf0d2a19e93f1d35dba4f Mon Sep 17 00:00:00 2001 From: jgaff Date: Fri, 5 Jan 2018 13:10:54 -0600 Subject: [PATCH 21/22] Python 2 syntax fix --- mdf_forge/forge.py | 3 ++- tests/test_forge.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mdf_forge/forge.py b/mdf_forge/forge.py index 9a642ba..daf218d 100644 --- a/mdf_forge/forge.py +++ b/mdf_forge/forge.py @@ -935,10 +935,11 @@ def http_stream(self, results, verbose=True): """ if self.__anonymous: print_("Error: Anonymous HTTP download not yet supported.") - return { + yield { "success": False, "message": "Anonymous HTTP download not yet supported." } + return # If results have info attached, remove it if type(results) is tuple: results = results[0] diff --git a/tests/test_forge.py b/tests/test_forge.py index d632bca..0f59963 100644 --- a/tests/test_forge.py +++ b/tests/test_forge.py @@ -889,7 +889,9 @@ def test_forge_anonymous(capsys): out, err = capsys.readouterr() assert "Error: Anonymous Globus Transfer not supported." in out # http_stream - with pytest.raises(StopIteration): - next(f.http_stream({})) + res = f.http_stream({}) + assert next(res)["success"] is False out, err = capsys.readouterr() assert "Error: Anonymous HTTP download not yet supported." in out + with pytest.raises(StopIteration): + next(res) From bab6f348db7e2b8d22be538887f99509f2f81cb7 Mon Sep 17 00:00:00 2001 From: jgaff Date: Mon, 8 Jan 2018 10:44:49 -0600 Subject: [PATCH 22/22] Test improvements, increment version --- setup.py | 2 +- tests/test_forge.py | 38 +++++++++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index ed0d029..fd1d7ca 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='mdf_forge', - version='0.5.0', + version='0.5.1', packages=['mdf_forge'], description='Materials Data Facility python package', long_description=("Forge is the Materials Data Facility Python package" diff --git a/tests/test_forge.py b/tests/test_forge.py index 0f59963..c0cd15c 100644 --- a/tests/test_forge.py +++ b/tests/test_forge.py @@ -170,6 +170,11 @@ def test_query_aggregate(capsys): out, err = capsys.readouterr() assert "Error: No query" in out + # Error on no index + assert q.aggregate(q="abc") == [] + out, err = capsys.readouterr() + assert "Error: No index specified" in out + # Basic aggregation res1 = q.aggregate("mdf.source_name:nist_xps_db", index="mdf") assert len(res1) > 10000 @@ -181,6 +186,10 @@ def test_query_aggregate(capsys): assert len(res2) > 10000 assert len(res2) > len(res1) + # Unnecessary aggregation fallback to .search() + # Check success in Coveralls + assert len(q.aggregate("mdf.source_name:hopv")) < 10000 + def test_query_chaining(): q = forge.Query(query_search_client) @@ -552,25 +561,32 @@ def test_forge_match_tags(): assert f.match_tags("") == f -def test_forge_match_years(capfd): +def test_forge_match_years(capsys): # One year of data/results f = forge.Forge(index="mdf") - years1 = "2015" - res1 = f.match_years(years1).search(limit=10) + res1 = f.match_years("2015").search() assert res1 != [] assert check_field(res1, "mdf.year", 2015) == 0 # Multiple years - years2 = ["2015", 2011] - res2 = f.match_years(years2).search() + res2 = f.match_years(years=["2015", 2011]).search() assert check_field(res2, "mdf.year", 2011) == 2 # Wrong input - years3 = ["20x5"] - f.match_years(years3).search() - out, err = capfd.readouterr() + f.match_years(["20x5"]).search() + out, err = capsys.readouterr() assert "Invalid year: '20x5'" in out + f.match_years(start="20x5").search() + out, err = capsys.readouterr() + assert "Invalid start year: '20x5'" in out + + f.match_years(stop="20x5").search() + out, err = capsys.readouterr() + assert "Invalid stop year: '20x5'" in out + + assert f.match_years() == f + # Test range res4 = f.match_years(start=2015, stop=2015, inclusive=True).search() assert check_field(res4, "mdf.year", 2015) == 0 @@ -611,7 +627,7 @@ def test_forge_search(capsys): assert "Error: No query" in out # Return info if requested - res2 = f.search(q="Al", info=False) + res2 = f.search(q="Al", info=False, index="mdf") assert isinstance(res2, list) assert isinstance(res2[0], dict) @@ -737,7 +753,7 @@ def test_forge_aggregate(): # And respects the reset_query arg f = forge.Forge(index="mdf") f.match_field("mdf.source_name", "nist_xps_db") - res1 = f.aggregate(reset_query=False) + res1 = f.aggregate(reset_query=False, index="mdf") assert len(res1) > 10000 assert check_field(res1, "mdf.source_name", "nist_xps_db") == 0 res2 = f.aggregate() @@ -867,7 +883,7 @@ def test_forge_show_fields(): f = forge.Forge(index="mdf") res1 = f.show_fields() assert "mdf" in res1.keys() - res2 = f.show_fields("mdf") + res2 = f.show_fields(block="mdf", index="mdf") assert "mdf.mdf_id" in res2.keys()