From bd9e37a81f43a5348288f4057dfc837b023145f6 Mon Sep 17 00:00:00 2001 From: Tyler Mathis <35553152+tsmathis@users.noreply.github.com> Date: Fri, 2 Oct 2026 11:17:55 -0700 Subject: [PATCH 1/2] bump emmet-core into next rc segment --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 464081fe..bca8c8d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ dependencies = [ "typing-extensions>=3.7.4.1", "requests>=2.23.0", "monty>=2024.12.10", - "emmet-core>=0.87.1", + "emmet-core>=0.87.3rc2,<0.87.4", "boto3", "orjson >= 3.10,<4", "pyarrow >= 20.0.0", @@ -50,7 +50,7 @@ contribs = [ ] all = [ "custodian", - "emmet-core[all]>=0.87.1", + "emmet-core[all]>=0.87.3rc2,<0.87.4", "fastmcp", "flask", ] From 423ae5a991fc40f03997f9bb5b5e46f1774e6a5b Mon Sep 17 00:00:00 2001 From: Tyler Mathis <35553152+tsmathis@users.noreply.github.com> Date: Fri, 2 Oct 2026 14:26:41 -0700 Subject: [PATCH 2/2] handle some warnings in tests --- tests/client/core/test_schemas.py | 24 +- tests/client/materials/test_electrodes.py | 29 +-- tests/client/molecules/test_jcesr.py | 21 +- tests/client/molecules/test_molecules.py | 15 +- tests/client/test_mprester.py | 254 ++++++++++++---------- 5 files changed, 191 insertions(+), 152 deletions(-) diff --git a/tests/client/core/test_schemas.py b/tests/client/core/test_schemas.py index 574e5229..55018a71 100644 --- a/tests/client/core/test_schemas.py +++ b/tests/client/core/test_schemas.py @@ -7,22 +7,22 @@ from mp_api.client.core.schemas import _DictLikeAccess, _convert_to_model -class TestClass(_DictLikeAccess): +class DummyClass(_DictLikeAccess): a: int b: float c: list[str] def test_dict_like_access(): - instance = TestClass(a=1, b=2.0, c=["a", "b", "c"]) + instance = DummyClass(a=1, b=2.0, c=["a", "b", "c"]) assert isinstance(instance, BaseModel) assert all( getattr(instance, field_name) == instance[field_name] and instance[field_name] == instance.get(field_name) - for field_name in TestClass.model_fields + for field_name in DummyClass.model_fields ) - as_str = """TestClass( + as_str = """DummyClass( a (int) : 1 b (float) : 2.0 c (list) : ['a', 'b', 'c'] @@ -30,7 +30,9 @@ def test_dict_like_access(): assert str(instance) == as_str assert repr(instance) == as_str - with pytest.raises(AttributeError, match="'TestClass' object has no attribute 'd'"): + with pytest.raises( + AttributeError, match="'DummyClass' object has no attribute 'd'" + ): instance.d assert instance.get("d", None) == None @@ -53,7 +55,7 @@ def test_model_generation(): {k: v for k, v in doc.items() if k != "b"} for doc in get_data() ), }.items(): - as_models = _convert_to_model(trial_data, TestClass, model_name=test_type) + as_models = _convert_to_model(trial_data, DummyClass, model_name=test_type) assert all(isinstance(doc, BaseModel) for doc in as_models) assert all(doc.__class__.__name__ == test_type for doc in as_models) @@ -73,20 +75,20 @@ def test_model_generation(): else: assert all( getattr(doc, k) and doc.get(k) - for k in TestClass.model_fields + for k in DummyClass.model_fields for doc in as_models ) assert all( substr in str(doc) - for substr in ("Fields not requested", "TestClass", test_type) + for substr in ("Fields not requested", "DummyClass", test_type) for doc in as_models ) # Test requesting unavailable fields as_models = _convert_to_model( [{k: v for k, v in doc.items() if k != "b"} for doc in get_data()], - TestClass, + DummyClass, requested_fields=["b"], ) @@ -96,7 +98,7 @@ def test_model_generation(): # Test accessing fields that weren't requested as_models = _convert_to_model( [{k: v for k, v in doc.items() if k == "b"} for doc in get_data()], - TestClass, + DummyClass, requested_fields=["b"], ) with pytest.raises( @@ -105,4 +107,4 @@ def test_model_generation(): as_models[0].a # Ensure graceful handling of empty iterator input (no docs returned) - assert _convert_to_model(iter([]), TestClass) == [] + assert _convert_to_model(iter([]), DummyClass) == [] diff --git a/tests/client/materials/test_electrodes.py b/tests/client/materials/test_electrodes.py index cb37d486..6e91b7f2 100644 --- a/tests/client/materials/test_electrodes.py +++ b/tests/client/materials/test_electrodes.py @@ -1,4 +1,5 @@ import os +import warnings import pytest from pymatgen.core.periodic_table import Element @@ -74,19 +75,21 @@ def test_insertion_client(insertion_rester): @requires_api_key def test_conversion_client(conversion_rester): - search_method = conversion_rester.search - - excl = ConversionElectrodeRester._exclude_search_fields - client_search_testing( - search_method=search_method, - excluded_params=excluded_params + excl, - alt_name_dict=alt_name_dict, - custom_field_tests={ - "battery_ids": ["mp-1067_Al"], - "working_ion": Element("Li"), - }, - sub_doc_fields=sub_doc_fields, - ) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=UserWarning) + search_method = conversion_rester.search + + excl = ConversionElectrodeRester._exclude_search_fields + client_search_testing( + search_method=search_method, + excluded_params=excluded_params + excl, + alt_name_dict=alt_name_dict, + custom_field_tests={ + "battery_ids": ["mp-1067_Al"], + "working_ion": Element("Li"), + }, + sub_doc_fields=sub_doc_fields, + ) @requires_api_key diff --git a/tests/client/molecules/test_jcesr.py b/tests/client/molecules/test_jcesr.py index cd7dd97b..50866f7a 100644 --- a/tests/client/molecules/test_jcesr.py +++ b/tests/client/molecules/test_jcesr.py @@ -1,4 +1,5 @@ import os +import warnings import pytest from pymatgen.core.periodic_table import Element @@ -15,9 +16,11 @@ @pytest.fixture def rester(): - rester = JcesrMoleculesRester() - yield rester - rester.session.close() + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + rester = JcesrMoleculesRester() + yield rester + rester.session.close() excluded_params = [ @@ -64,8 +67,10 @@ def test_warning(): @requires_api_key def test_pagination(): - with JcesrMoleculesRester() as rester: - client_pagination(rester.search, "task_id") + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + with JcesrMoleculesRester() as rester: + client_pagination(rester.search, "task_id") @requires_api_key @@ -76,5 +81,7 @@ def test_pagination(): ], ) def test_sort(sort_field): - with JcesrMoleculesRester() as rester: - client_sort(rester.search, sort_field, default_fields=()) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + with JcesrMoleculesRester() as rester: + client_sort(rester.search, sort_field, default_fields=()) diff --git a/tests/client/molecules/test_molecules.py b/tests/client/molecules/test_molecules.py index 5d5cd098..88643a8d 100644 --- a/tests/client/molecules/test_molecules.py +++ b/tests/client/molecules/test_molecules.py @@ -4,15 +4,18 @@ point for other resters, therefore we include only basic tests here. """ +import warnings from mp_api.client.routes.molecules.molecules import MoleculeRester from mp_api.client.routes.molecules import MOLECULES_RESTERS def test_molecule_rester(): - with MoleculeRester() as rester: - assert all(sub_rester in dir(rester) for sub_rester in MOLECULES_RESTERS) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + with MoleculeRester() as rester: + assert all(sub_rester in dir(rester) for sub_rester in MOLECULES_RESTERS) - assert all( - getattr(rester, k)._class_name == lazy_obj._class_name - for k, lazy_obj in MOLECULES_RESTERS.items() - ) + assert all( + getattr(rester, k)._class_name == lazy_obj._class_name + for k, lazy_obj in MOLECULES_RESTERS.items() + ) diff --git a/tests/client/test_mprester.py b/tests/client/test_mprester.py index 1b807cd6..3dd0b035 100644 --- a/tests/client/test_mprester.py +++ b/tests/client/test_mprester.py @@ -35,7 +35,9 @@ ConstantEnergyAdjustment, GibbsComputedStructureEntry, ) -from pymatgen.entries.mixing_scheme import MaterialsProjectDFTMixingScheme +from pymatgen.analysis.compatibility.mixing_scheme import ( + MaterialsProjectDFTMixingScheme, +) from pymatgen.io.cif import CifParser from pymatgen.io.vasp import Chgcar @@ -144,46 +146,53 @@ def test_find_structure( _atom_site_occupancy Ne Ne0 1 0.00000000 0.00000000 -0.00000000 1 """ - struct_from_cif = CifParser.from_str(cif_str).parse_structures(primitive=True)[ - 0 - ] - temp_file = NamedTemporaryFile(suffix=".cif") - with open(temp_file.name, "wt") as f: - f.write(cif_str) - f.seek(0) - - for struct_or_path, use_document_model in [ - (temp_file.name, True), - (struct_from_cif, False), - ]: - with MPRester(use_document_model=use_document_model) as mpr: - data = mpr.find_structure(struct_or_path) - assert isinstance(data, str) and data == "mp-111" - f.close() + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=UserWarning) + struct_from_cif = CifParser.from_str(cif_str).parse_structures( + primitive=True + )[0] + temp_file = NamedTemporaryFile(suffix=".cif") + with open(temp_file.name, "wt") as f: + f.write(cif_str) + f.seek(0) + + for struct_or_path, use_document_model in [ + (temp_file.name, True), + (struct_from_cif, False), + ]: + with MPRester(use_document_model=use_document_model) as mpr: + data = mpr.find_structure(struct_or_path) + assert isinstance(data, str) and data == "mp-111" + + f.close() - with pytest.raises(MPRestError, match="Provide filename or Structure object."): - mpr.find_structure(struct_from_cif.as_dict()) + with pytest.raises( + MPRestError, match="Provide filename or Structure object." + ): + mpr.find_structure(struct_from_cif.as_dict()) - with pytest.raises(MPRestError, match="`allow_multiple_results` must be a"): - mpr.find_structure(struct_from_cif, allow_multiple_results=1.0) + with pytest.raises(MPRestError, match="`allow_multiple_results` must be a"): + mpr.find_structure(struct_from_cif, allow_multiple_results=1.0) - assert ( - len( - mpr.find_structure( - struct_from_cif.copy().replace_species({"Ne": "K"}), - allow_multiple_results=2, + assert ( + len( + mpr.find_structure( + struct_from_cif.copy().replace_species({"Ne": "K"}), + allow_multiple_results=2, + ) ) + <= 2 ) - <= 2 - ) def test_get_bandstructure_by_material_id(self, mpr): - bs = mpr.get_bandstructure_by_material_id("mp-149") - assert isinstance(bs, BandStructureSymmLine) - bs_uniform = mpr.get_bandstructure_by_material_id("mp-149", line_mode=False) - assert isinstance(bs_uniform, BandStructure) - assert not isinstance(bs_uniform, BandStructureSymmLine) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=UserWarning) + bs = mpr.get_bandstructure_by_material_id("mp-149") + assert isinstance(bs, BandStructureSymmLine) + bs_uniform = mpr.get_bandstructure_by_material_id("mp-149", line_mode=False) + assert isinstance(bs_uniform, BandStructure) + assert not isinstance(bs_uniform, BandStructureSymmLine) def test_get_dos_by_id(self, mpr): dos = mpr.get_dos_by_material_id("mp-149") @@ -282,7 +291,9 @@ def test_get_entries_in_chemsys(self, mpr): MPRestWarning, match="The default thermo type when retrieving entries" ): entries = mpr.get_entries_in_chemsys(syms) - entries2 = mpr.get_entries_in_chemsys(syms2) + entries2 = mpr.get_entries_in_chemsys( + syms2, additional_criteria={"thermo_types": [ThermoType.GGA_GGA_U_R2SCAN]} + ) elements = {Element(sym) for sym in syms} for e in entries: assert isinstance(e, ComputedEntry) @@ -292,7 +303,11 @@ def test_get_entries_in_chemsys(self, mpr): e2 = {i.entry_id for i in entries2} assert e1 == e2 - gibbs_entries = mpr.get_entries_in_chemsys(syms2, use_gibbs=500) + gibbs_entries = mpr.get_entries_in_chemsys( + syms2, + use_gibbs=500, + additional_criteria={"thermo_types": [ThermoType.GGA_GGA_U_R2SCAN]}, + ) for e in gibbs_entries: assert isinstance(e, GibbsComputedStructureEntry) @@ -308,7 +323,10 @@ def test_get_entries_in_chemsys_mixed_hull(self, mpr): chemical system that entry's thermo doc was built for, so pooling the served entries across subsystems put Cs2TiI6 ~4.6 eV/atom above the hull instead of on it. """ - entries = mpr.get_entries_in_chemsys("Cs-Ti-I") + entries = mpr.get_entries_in_chemsys( + "Cs-Ti-I", + additional_criteria={"thermo_types": [ThermoType.GGA_GGA_U_R2SCAN]}, + ) phase_diagram = PhaseDiagram(entries) host = next(e for e in entries if e.composition.reduced_formula == "Cs2TiI6") assert phase_diagram.get_e_above_hull(host) == pytest.approx(0.0, abs=1e-6) @@ -334,7 +352,9 @@ def test_get_entries_in_chemsys_mixed_hull(self, mpr): all_fields=False, fields=["material_id", "energy_above_hull"], ) - entries = mpr.get_entries_in_chemsys("H-O") + entries = mpr.get_entries_in_chemsys( + "H-O", additional_criteria={"thermo_types": [ThermoType.GGA_GGA_U_R2SCAN]} + ) phase_diagram = PhaseDiagram(entries) by_mpid = defaultdict(list) for entry in entries: @@ -352,7 +372,11 @@ def test_get_entries_in_chemsys_mixed_hull(self, mpr): # uncorrected mixed entries cannot be placed on a common scale, so a warning is thrown: with pytest.warns(MPRestWarning, match="common energy scale"): - mpr.get_entries_in_chemsys("Cs-Ti-I", compatible_only=False) + mpr.get_entries_in_chemsys( + "Cs-Ti-I", + compatible_only=False, + additional_criteria={"thermo_types": [ThermoType.GGA_GGA_U_R2SCAN]}, + ) def test_get_entries_in_chemsys_decorated_served_pd(self, mpr): """ @@ -360,9 +384,14 @@ def test_get_entries_in_chemsys_decorated_served_pd(self, mpr): the pre-built phase diagram (and decorated post-hoc), rather than falling back to re-applying the mixing scheme locally (which can differ from MP's hull). """ - entries = mpr.get_entries_in_chemsys("H-O") + entries = mpr.get_entries_in_chemsys( + "H-O", additional_criteria={"thermo_types": [ThermoType.GGA_GGA_U_R2SCAN]} + ) decorated = mpr.get_entries_in_chemsys( - "H-O", property_data=["energy_above_hull"], conventional_unit_cell=True + "H-O", + property_data=["energy_above_hull"], + conventional_unit_cell=True, + additional_criteria={"thermo_types": [ThermoType.GGA_GGA_U_R2SCAN]}, ) served = { str(e.entry_id): ( @@ -662,8 +691,6 @@ def test_get_cohesive_energy(self): with MPRester() as mpr: mpr.get_cohesive_energy("mp-1") - # SOMETHING IS OFF HERE FOR THE MIXING SCHEME - # MIXING SCHEME TEST IS FLAKY, PASSES ROUGHLY 20% OF THE TIME @pytest.mark.parametrize( "thermo_type", ["GGA_GGA+U", ThermoType.GGA_GGA_U_R2SCAN, "r2SCAN"] ) @@ -673,83 +700,80 @@ def test_get_stability(self, thermo_type): to include more diverse chemical environments and thermo types which reflect the scope of the current MP database. """ - if ( - isinstance(thermo_type, ThermoType) - and thermo_type == ThermoType.GGA_GGA_U_R2SCAN - ): - pytest.skip("See comments about flakiness for mixing scheme") - with MPRester() as mpr: - - # No golden test data. Always test on fetched thermo data - chemsys_to_test: set[str] = { - doc.chemsys - for doc in mpr.materials.thermo.search( - thermo_types=[thermo_type], - num_elements=2, - num_chunks=1, - chunk_size=4, - fields=["chemsys"], - ) - } + with warnings.catch_warnings(): + # ignore some common pmg warnings: failed to guess oxi states, discarding entries, etc. + warnings.filterwarnings("ignore", category=UserWarning) - for chemsys in chemsys_to_test: - - # RETURN ORDER NOT DETERMINISTIC - entries = mpr.get_entries_in_chemsys( - chemsys, additional_criteria={"thermo_types": [thermo_type]} - ) - - modified_entries = [ - ComputedEntry( - entry.composition, - entry.uncorrected_energy + 0.01, - parameters=entry.parameters, - entry_id=f"mod_{entry.entry_id}", + with MPRester() as mpr: + # No golden test data. Always test on fetched thermo data + chemsys_to_test: set[str] = { + doc.chemsys + for doc in mpr.materials.thermo.search( + thermo_types=[thermo_type], + num_elements=2, + num_chunks=1, + chunk_size=4, + fields=["chemsys"], ) - for entry in entries - # MIXING SCHEME - ONLY PASSES IF A "GOOD" ENTRY IS RETURNED FIRST?? - if entry.entry_id == entries[0].entry_id - ] - - if ( - all(len(entry.composition.elements) == 1 for entry in entries) - and chemsys.count("-") > 0 - ): - # For a multi-element chemsys with no multinaries, only elementals, - # there should be no phase diagram data available. - with pytest.warns( - MPRestWarning, match="No phase diagram data available" - ): - mpr.get_stability(modified_entries, thermo_type=thermo_type) - return + } - else: - rester_ehulls = mpr.get_stability( - modified_entries, thermo_type=thermo_type + for chemsys in chemsys_to_test: + # RETURN ORDER NOT DETERMINISTIC + entries = mpr.get_entries_in_chemsys( + chemsys, additional_criteria={"thermo_types": [thermo_type]} ) - all_entries = entries + modified_entries - - compat = None - if thermo_type == "GGA_GGA+U": - compat = MaterialsProject2020Compatibility() - elif thermo_type == "GGA_GGA+U_R2SCAN": - compat = MaterialsProjectDFTMixingScheme(run_type_2="r2SCAN") - - if compat: - all_entries = compat.process_entries(all_entries) - - pd = PhaseDiagram(all_entries) - for entry in all_entries: - if str(entry.entry_id).startswith("mod"): - for dct in rester_ehulls: - if dct["entry_id"] == entry.entry_id: - data = dct - break - assert pd.get_e_above_hull(entry) == pytest.approx( - data["e_above_hull"] - ) + modified_entries = [ + ComputedEntry( + entry.composition, + entry.uncorrected_energy + 0.01, + parameters=entry.parameters, + entry_id=f"mod_{entry.entry_id}", + ) + for entry in entries + # MIXING SCHEME - ONLY PASSES IF A "GOOD" ENTRY IS RETURNED FIRST?? + if entry.entry_id == entries[0].entry_id + ] + + if ( + all(len(entry.composition.elements) == 1 for entry in entries) + and chemsys.count("-") > 0 + ): + # For a multi-element chemsys with no multinaries, only elementals, + # there should be no phase diagram data available. + with pytest.warns( + MPRestWarning, match="No phase diagram data available" + ): + mpr.get_stability(modified_entries, thermo_type=thermo_type) + return + + else: + rester_ehulls = mpr.get_stability( + modified_entries, thermo_type=thermo_type + ) + + all_entries = entries + modified_entries + + compat = None + if thermo_type == "GGA_GGA+U": + compat = MaterialsProject2020Compatibility() + elif thermo_type == "GGA_GGA+U_R2SCAN": + compat = MaterialsProjectDFTMixingScheme(run_type_2="r2SCAN") + + if compat: + all_entries = compat.process_entries(all_entries) + + pd = PhaseDiagram(all_entries) + for entry in all_entries: + if str(entry.entry_id).startswith("mod"): + for dct in rester_ehulls: + if dct["entry_id"] == entry.entry_id: + data = dct + break + assert pd.get_e_above_hull(entry) == pytest.approx( + data["e_above_hull"] + ) @pytest.mark.parametrize( "mpid, working_ion, thermo_type", @@ -861,7 +885,7 @@ def test_warnings_exceptions(self): with MPRester() as mpr: with pytest.raises( NotImplementedError, - match="The MPRester\(\).query method has been replaced", + match=r"The MPRester\(\).query method has been replaced", ): mpr.query(some_field=1.0) @@ -872,7 +896,7 @@ def test_warnings_exceptions(self): for attr in mpr._deprecated_attributes: with pytest.warns( - DeprecationWarning, match="Accessing.*data through MPRester\..*" + DeprecationWarning, match=r"Accessing.*data through MPRester\..*" ): getattr(mpr, attr, None)