From a994740de3b95fdd078cf78b1f0af6f3e6c20d89 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Fri, 17 Oct 2025 15:56:57 -0700 Subject: [PATCH 1/8] fix structure retrieval --- mp_api/client/routes/materials/materials.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mp_api/client/routes/materials/materials.py b/mp_api/client/routes/materials/materials.py index df24e93b..b1e43f42 100644 --- a/mp_api/client/routes/materials/materials.py +++ b/mp_api/client/routes/materials/materials.py @@ -131,6 +131,14 @@ def get_structure_by_material_id( response[0].model_dump() if self.use_document_model else response[0] # type: ignore ) + # Ensure that return type is a Structure regardless of `monty_decode` or `model_dump` output + if isinstance(response[field],dict): + response[field] = Structure.from_dict(response[field]) + elif isinstance(response[field],list) and any(isinstance(struct,dict) for struct in response[field]): + response[field] = [ + Structure.from_dict(struct) for struct in response[field] + ] + return response[field] if response else response # type: ignore def search( From 10bb7240ff4fcb63d9f0d901497e993b49acb163 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Fri, 17 Oct 2025 16:20:43 -0700 Subject: [PATCH 2/8] precommit --- mp_api/client/routes/materials/materials.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mp_api/client/routes/materials/materials.py b/mp_api/client/routes/materials/materials.py index b1e43f42..5f2c68bb 100644 --- a/mp_api/client/routes/materials/materials.py +++ b/mp_api/client/routes/materials/materials.py @@ -132,9 +132,11 @@ def get_structure_by_material_id( ) # Ensure that return type is a Structure regardless of `monty_decode` or `model_dump` output - if isinstance(response[field],dict): + if isinstance(response[field], dict): response[field] = Structure.from_dict(response[field]) - elif isinstance(response[field],list) and any(isinstance(struct,dict) for struct in response[field]): + elif isinstance(response[field], list) and any( + isinstance(struct, dict) for struct in response[field] + ): response[field] = [ Structure.from_dict(struct) for struct in response[field] ] From f82b5013efd67b7f465ed4642ccf49072c2f990d Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Mon, 20 Oct 2025 13:49:34 -0700 Subject: [PATCH 3/8] de-duplicate output of get_entries --- mp_api/client/mprester.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/mp_api/client/mprester.py b/mp_api/client/mprester.py index 3fdc07f9..1c539f94 100644 --- a/mp_api/client/mprester.py +++ b/mp_api/client/mprester.py @@ -723,7 +723,7 @@ def get_entries( if additional_criteria: input_params = {**input_params, **additional_criteria} - entries = [] + entries: set[ComputedStructureEntry] = set() fields = ( ["entries", "thermo_type"] @@ -744,18 +744,18 @@ def get_entries( else doc["entries"].values() # type: ignore ) for entry in entry_list: - entry_dict: dict = entry.as_dict() if self.monty_decode else entry # type: ignore + entry_dict: dict = entry.as_dict() if hasattr(entry, "as_dict") else entry # type: ignore if not compatible_only: entry_dict["correction"] = 0.0 entry_dict["energy_adjustments"] = [] if property_data: - for property in property_data: - entry_dict["data"][property] = ( - doc.model_dump()[property] # type: ignore - if self.use_document_model - else doc[property] # type: ignore - ) + entry_dict["data"] = { + property: getattr(doc, property, None) + if self.use_document_model + else doc[property] + for property in property_data + } if conventional_unit_cell: entry_struct = Structure.from_dict(entry_dict["structure"]) @@ -776,15 +776,10 @@ def get_entries( if "n_atoms" in correction: correction["n_atoms"] *= site_ratio - entry = ( - ComputedStructureEntry.from_dict(entry_dict) - if self.monty_decode - else entry_dict - ) - - entries.append(entry) + # Need to store object to permit de-duplication + entries.add(ComputedStructureEntry.from_dict(entry_dict)) - return entries + return [e if self.monty_decode else e.as_dict() for e in entries] def get_pourbaix_entries( self, From 34931403f6a6c089357fc110e8335f8e1fcc62f5 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Tue, 21 Oct 2025 16:32:10 -0700 Subject: [PATCH 4/8] allow MPDataDoc to have dict-like access to model fields, add a .get, and clean up use_document_model boilerplate --- mp_api/client/core/client.py | 34 ++++++-- mp_api/client/mprester.py | 81 +++++-------------- .../routes/materials/electronic_structure.py | 71 ++++++---------- mp_api/client/routes/materials/materials.py | 5 +- mp_api/client/routes/molecules/molecules.py | 8 +- tests/test_mprester.py | 13 ++- 6 files changed, 84 insertions(+), 128 deletions(-) diff --git a/mp_api/client/core/client.py b/mp_api/client/core/client.py index 4cd98958..600ee1c2 100644 --- a/mp_api/client/core/client.py +++ b/mp_api/client/core/client.py @@ -63,6 +63,23 @@ SETTINGS = MAPIClientSettings() # type: ignore +class _DictLikeAccess(BaseModel): + """Define a pydantic mix-in which permits dict-like access to model fields.""" + + def __getitem__(self, item : str) -> Any: + """Return `item` if a valid model field, otherwise raise an exception.""" + if item in self.__class__.model_fields: + return getattr(self,item) + raise AttributeError( + f"{self.__class__.__name__} has no model field `{item}`." + ) + + def get(self, item : str, default : Any = None) -> Any: + """Return a model field `item`, or `default` if it doesn't exist.""" + try: + return self.__getitem__(item) + except AttributeError: + return default class BaseRester: """Base client class with core stubs.""" @@ -427,13 +444,9 @@ def _query_resource( if use_document_model is None: use_document_model = self.use_document_model - if timeout is None: - timeout = self.timeout + timeout = self.timeout if timeout is None else timeout - if criteria: - criteria = {k: v for k, v in criteria.items() if v is not None} - else: - criteria = {} + criteria = {k: v for k, v in (criteria or {}).items() if v is not None} # Query s3 if no query is passed and all documents are asked for # TODO also skip fields set to same as their default @@ -1074,12 +1087,21 @@ def _generate_returned_model( field_copy, ) + validators = {} + for k in ("field_validators","model_validators","root_validators","validators"): + validators.update({ + k : v.func + for k, v in getattr(self.document_model.__pydantic_decorators__,k,{}).items() + if hasattr(v,"func") + }) + data_model = create_model( # type: ignore "MPDataDoc", **include_fields, # TODO fields_not_requested is not the same as unset_fields # i.e. field could be requested but not available in the raw doc fields_not_requested=(list[str], unset_fields), + __base__=_DictLikeAccess, __doc__=".".join( [ getattr(self.document_model, k, "") diff --git a/mp_api/client/mprester.py b/mp_api/client/mprester.py index 1c539f94..3980fd94 100644 --- a/mp_api/client/mprester.py +++ b/mp_api/client/mprester.py @@ -1,5 +1,6 @@ from __future__ import annotations +from collections import defaultdict import itertools import os import warnings @@ -424,11 +425,7 @@ def get_task_ids_associated_with_material_id( if not tasks: return [] - calculations = ( - tasks[0].calc_types # type: ignore - if self.use_document_model - else tasks[0]["calc_types"] # type: ignore - ) + calculations = tasks[0]["calc_types"] if calc_types: return [ @@ -436,8 +433,7 @@ def get_task_ids_associated_with_material_id( for task, calc_type in calculations.items() if calc_type in calc_types ] - else: - return list(calculations.keys()) + return list(calculations.keys()) def get_structure_by_material_id( self, material_id: str, final: bool = True, conventional_unit_cell: bool = False @@ -539,11 +535,7 @@ def get_material_id_references(self, material_id: str) -> list[str]: List of BibTeX references ([str]) """ docs = self.materials.provenance.search(material_ids=material_id) - - if not docs: - return [] - - return docs[0].references if self.use_document_model else docs[0]["references"] # type: ignore + return docs[0]["references"] if docs else [] def get_material_ids( self, @@ -558,17 +550,16 @@ def get_material_ids( Returns: List of all materials ids ([MPID]) """ + inp_k = "formula" if isinstance(chemsys_formula, list) or ( isinstance(chemsys_formula, str) and "-" in chemsys_formula ): - input_params = {"chemsys": chemsys_formula} - else: - input_params = {"formula": chemsys_formula} + inp_k = "chemsys" return sorted( - doc.material_id if self.use_document_model else doc["material_id"] # type: ignore + doc["material_id"] for doc in self.materials.search( - **input_params, # type: ignore + **{inp_k : chemsys_formula}, all_fields=False, fields=["material_id"], ) @@ -601,10 +592,8 @@ def get_structures( all_fields=False, fields=["structure"], ) - if not self.use_document_model: - return [doc["structure"] for doc in docs] # type: ignore - return [doc.structure for doc in docs] # type: ignore + return [doc["structure"] for doc in docs] else: structures = [] @@ -613,12 +602,7 @@ def get_structures( all_fields=False, fields=["initial_structures"], ): - initial_structures = ( - doc.initial_structures # type: ignore - if self.use_document_model - else doc["initial_structures"] # type: ignore - ) - structures.extend(initial_structures) + structures.extend(doc["initial_structures"]) return structures @@ -738,11 +722,7 @@ def get_entries( ) for doc in docs: - entry_list = ( - doc.entries.values() # type: ignore - if self.use_document_model - else doc["entries"].values() # type: ignore - ) + entry_list = doc["entries"].values() for entry in entry_list: entry_dict: dict = entry.as_dict() if hasattr(entry, "as_dict") else entry # type: ignore if not compatible_only: @@ -750,13 +730,8 @@ def get_entries( entry_dict["energy_adjustments"] = [] if property_data: - entry_dict["data"] = { - property: getattr(doc, property, None) - if self.use_document_model - else doc[property] - for property in property_data - } - + entry_dict["data"] = {property: doc[property] for property in property_data} + if conventional_unit_cell: entry_struct = Structure.from_dict(entry_dict["structure"]) s = SpacegroupAnalyzer( @@ -1310,9 +1285,7 @@ def get_wulff_shape(self, material_id: str): if not doc: return None - surfaces: list = ( - doc[0].surfaces if self.use_document_model else doc[0]["surfaces"] # type: ignore - ) + surfaces: list = doc[0]["surfaces"] lattice = ( SpacegroupAnalyzer(structure).get_conventional_standard_structure().lattice @@ -1382,17 +1355,8 @@ def get_charge_density_from_material_id( if len(results) == 0: return None - latest_doc = max( # type: ignore - results, - key=lambda x: ( - x.last_updated # type: ignore - if self.use_document_model - else x["last_updated"] - ), # type: ignore - ) - task_id = ( - latest_doc.task_id if self.use_document_model else latest_doc["task_id"] - ) + latest_doc = max(results, key=lambda x: x["last_updated"]) + task_id = latest_doc["task_id"] return self.get_charge_density_from_task_id(task_id, inc_task_doc) def get_download_info(self, material_ids, calc_types=None, file_patterns=None): @@ -1414,20 +1378,17 @@ def get_download_info(self, material_ids, calc_types=None, file_patterns=None): else [] ) - meta = {} + meta = defaultdict(list) for doc in self.materials.search( # type: ignore task_ids=material_ids, fields=["calc_types", "deprecated_tasks", "material_id"], ): - doc_dict: dict = doc.model_dump() if self.use_document_model else doc # type: ignore - for task_id, calc_type in doc_dict["calc_types"].items(): + for task_id, calc_type in doc["calc_types"].items(): if calc_types and calc_type not in calc_types: continue - mp_id = doc_dict["material_id"] - if meta.get(mp_id) is None: - meta[mp_id] = [{"task_id": task_id, "calc_type": calc_type}] - else: - meta[mp_id].append({"task_id": task_id, "calc_type": calc_type}) + mp_id = doc["material_id"] + meta[mp_id].append({"task_id": task_id, "calc_type": calc_type}) + if not meta: raise ValueError(f"No tasks found for material id {material_ids}.") diff --git a/mp_api/client/routes/materials/electronic_structure.py b/mp_api/client/routes/materials/electronic_structure.py index ccaaaad8..d7cdc616 100644 --- a/mp_api/client/routes/materials/electronic_structure.py +++ b/mp_api/client/routes/materials/electronic_structure.py @@ -276,61 +276,46 @@ def get_bandstructure_from_material_id( if not bs_doc: raise MPRestError("No electronic structure data found.") - bs_data = ( - bs_doc[0].bandstructure # type: ignore - if self.use_document_model - else bs_doc[0]["bandstructure"] # type: ignore - ) - - if bs_data is None: + if (bs_data := bs_doc[0]["bandstructure"]) is None: raise MPRestError( f"No {path_type.value} band structure data found for {material_id}" ) - else: - bs_data: dict = ( - bs_data.model_dump() if self.use_document_model else bs_data # type: ignore - ) + + bs_data: dict = ( + bs_data.model_dump() if self.use_document_model else bs_data # type: ignore + ) - if bs_data.get(path_type.value, None): - bs_task_id = bs_data[path_type.value]["task_id"] - else: + if bs_data.get(path_type.value, None) is None: raise MPRestError( f"No {path_type.value} band structure data found for {material_id}" ) + bs_task_id = bs_data[path_type.value]["task_id"] + else: - bs_doc = es_rester.search(material_ids=material_id, fields=["dos"]) - if not bs_doc: + if not (bs_doc := es_rester.search(material_ids=material_id, fields=["dos"])): raise MPRestError("No electronic structure data found.") - bs_data = ( - bs_doc[0].dos # type: ignore - if self.use_document_model - else bs_doc[0]["dos"] # type: ignore - ) - - if bs_data is None: + if (bs_data := bs_doc[0]["dos"]) is None: raise MPRestError( f"No uniform band structure data found for {material_id}" ) - else: - bs_data: dict = ( - bs_data.model_dump() if self.use_document_model else bs_data # type: ignore - ) + + bs_data: dict = ( + bs_data.model_dump() if self.use_document_model else bs_data # type: ignore + ) - if bs_data.get("total", None): - bs_task_id = bs_data["total"]["1"]["task_id"] - else: + if bs_data.get("total", None) is None: raise MPRestError( f"No uniform band structure data found for {material_id}" ) + bs_task_id = bs_data["total"]["1"]["task_id"] bs_obj = self.get_bandstructure_from_task_id(bs_task_id) if bs_obj: return bs_obj - else: - raise MPRestError("No band structure object found.") + raise MPRestError("No band structure object found.") class DosRester(BaseRester): @@ -456,22 +441,14 @@ def get_dos_from_material_id(self, material_id: str): mute_progress_bars=self.mute_progress_bars, ) - dos_doc = es_rester.search(material_ids=material_id, fields=["dos"]) - if not dos_doc: + if not (dos_doc := es_rester.search(material_ids=material_id, fields=["dos"])): return None - dos_data: dict = ( - dos_doc[0].model_dump() if self.use_document_model else dos_doc[0] # type: ignore - ) - - if dos_data["dos"]: - dos_task_id = dos_data["dos"]["total"]["1"]["task_id"] - else: + if not (dos_data := dos_doc[0].get("dos")): raise MPRestError(f"No density of states data found for {material_id}") - - dos_obj = self.get_dos_from_task_id(dos_task_id) - - if dos_obj: + + dos_task_id = (dos_data.model_dump() if self.use_document_model else dos_data)["total"]["1"]["task_id"] + if (dos_obj := self.get_dos_from_task_id(dos_task_id)): return dos_obj - else: - raise MPRestError("No density of states object found.") + + raise MPRestError("No density of states object found.") diff --git a/mp_api/client/routes/materials/materials.py b/mp_api/client/routes/materials/materials.py index 5f2c68bb..9d54f619 100644 --- a/mp_api/client/routes/materials/materials.py +++ b/mp_api/client/routes/materials/materials.py @@ -127,10 +127,7 @@ def get_structure_by_material_id( response = self.search(material_ids=material_id, fields=[field]) if response: - response = ( - response[0].model_dump() if self.use_document_model else response[0] # type: ignore - ) - + response = response[0] # Ensure that return type is a Structure regardless of `monty_decode` or `model_dump` output if isinstance(response[field], dict): response[field] = Structure.from_dict(response[field]) diff --git a/mp_api/client/routes/molecules/molecules.py b/mp_api/client/routes/molecules/molecules.py index 922f136e..5553a526 100644 --- a/mp_api/client/routes/molecules/molecules.py +++ b/mp_api/client/routes/molecules/molecules.py @@ -34,13 +34,7 @@ def get_molecule_by_mpculeid( field = "molecule" if final else "initial_molecules" response = self.search(molecule_ids=[mpcule_id], fields=[field]) # type: ignore - - if response: - response = ( - response[0].model_dump() if self.use_document_model else response[0] # type: ignore - ) - - return response[field] if response else response # type: ignore + return response[0][field] if response else response # type: ignore def find_molecule( self, diff --git a/tests/test_mprester.py b/tests/test_mprester.py index 0cc9d271..6b2955e3 100644 --- a/tests/test_mprester.py +++ b/tests/test_mprester.py @@ -148,7 +148,10 @@ def test_get_entries(self, mpr): assert e.data.get("energy_above_hull", None) is not None # Conventional structure - entry = mpr.get_entry_by_material_id("mp-22526", conventional_unit_cell=True)[1] + entry = next( + e for e in mpr.get_entry_by_material_id("mp-22526", conventional_unit_cell=True) + if e.entry_id == "mp-22526-r2SCAN" + ) s = entry.structure assert pytest.approx(s.lattice.a) == s.lattice.b @@ -158,9 +161,11 @@ def test_get_entries(self, mpr): assert pytest.approx(s.lattice.gamma) == 120 # Ensure energy per atom is same - prim = mpr.get_entry_by_material_id("mp-22526", conventional_unit_cell=False)[1] - - s = prim.structure + entry = next( + e for e in mpr.get_entry_by_material_id("mp-22526", conventional_unit_cell=False) + if e.entry_id == "mp-22526-r2SCAN" + ) + s = entry.structure assert pytest.approx(s.lattice.a) == s.lattice.b assert pytest.approx(s.lattice.a, abs=1e-3) == s.lattice.c assert pytest.approx(s.lattice.alpha, abs=1e-3) == s.lattice.beta From fe3f45ad8b3da4a2808ea9ab3d21f54615835a3f Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Tue, 21 Oct 2025 16:32:30 -0700 Subject: [PATCH 5/8] precommit --- mp_api/client/core/client.py | 35 ++++++++++++------- mp_api/client/mprester.py | 12 ++++--- .../routes/materials/electronic_structure.py | 21 ++++++----- tests/test_mprester.py | 10 ++++-- 4 files changed, 49 insertions(+), 29 deletions(-) diff --git a/mp_api/client/core/client.py b/mp_api/client/core/client.py index 600ee1c2..69eea260 100644 --- a/mp_api/client/core/client.py +++ b/mp_api/client/core/client.py @@ -63,24 +63,24 @@ SETTINGS = MAPIClientSettings() # type: ignore + class _DictLikeAccess(BaseModel): """Define a pydantic mix-in which permits dict-like access to model fields.""" - def __getitem__(self, item : str) -> Any: + def __getitem__(self, item: str) -> Any: """Return `item` if a valid model field, otherwise raise an exception.""" if item in self.__class__.model_fields: - return getattr(self,item) - raise AttributeError( - f"{self.__class__.__name__} has no model field `{item}`." - ) - - def get(self, item : str, default : Any = None) -> Any: + return getattr(self, item) + raise AttributeError(f"{self.__class__.__name__} has no model field `{item}`.") + + def get(self, item: str, default: Any = None) -> Any: """Return a model field `item`, or `default` if it doesn't exist.""" try: return self.__getitem__(item) except AttributeError: return default + class BaseRester: """Base client class with core stubs.""" @@ -1088,12 +1088,21 @@ def _generate_returned_model( ) validators = {} - for k in ("field_validators","model_validators","root_validators","validators"): - validators.update({ - k : v.func - for k, v in getattr(self.document_model.__pydantic_decorators__,k,{}).items() - if hasattr(v,"func") - }) + for k in ( + "field_validators", + "model_validators", + "root_validators", + "validators", + ): + validators.update( + { + k: v.func + for k, v in getattr( + self.document_model.__pydantic_decorators__, k, {} + ).items() + if hasattr(v, "func") + } + ) data_model = create_model( # type: ignore "MPDataDoc", diff --git a/mp_api/client/mprester.py b/mp_api/client/mprester.py index 3980fd94..e6f65c96 100644 --- a/mp_api/client/mprester.py +++ b/mp_api/client/mprester.py @@ -1,9 +1,9 @@ from __future__ import annotations -from collections import defaultdict import itertools import os import warnings +from collections import defaultdict from functools import cache, lru_cache from typing import TYPE_CHECKING @@ -559,7 +559,7 @@ def get_material_ids( return sorted( doc["material_id"] for doc in self.materials.search( - **{inp_k : chemsys_formula}, + **{inp_k: chemsys_formula}, all_fields=False, fields=["material_id"], ) @@ -730,8 +730,10 @@ def get_entries( entry_dict["energy_adjustments"] = [] if property_data: - entry_dict["data"] = {property: doc[property] for property in property_data} - + entry_dict["data"] = { + property: doc[property] for property in property_data + } + if conventional_unit_cell: entry_struct = Structure.from_dict(entry_dict["structure"]) s = SpacegroupAnalyzer( @@ -1388,7 +1390,7 @@ def get_download_info(self, material_ids, calc_types=None, file_patterns=None): continue mp_id = doc["material_id"] meta[mp_id].append({"task_id": task_id, "calc_type": calc_type}) - + if not meta: raise ValueError(f"No tasks found for material id {material_ids}.") diff --git a/mp_api/client/routes/materials/electronic_structure.py b/mp_api/client/routes/materials/electronic_structure.py index d7cdc616..94298206 100644 --- a/mp_api/client/routes/materials/electronic_structure.py +++ b/mp_api/client/routes/materials/electronic_structure.py @@ -280,7 +280,7 @@ def get_bandstructure_from_material_id( raise MPRestError( f"No {path_type.value} band structure data found for {material_id}" ) - + bs_data: dict = ( bs_data.model_dump() if self.use_document_model else bs_data # type: ignore ) @@ -290,17 +290,18 @@ def get_bandstructure_from_material_id( f"No {path_type.value} band structure data found for {material_id}" ) bs_task_id = bs_data[path_type.value]["task_id"] - - else: - if not (bs_doc := es_rester.search(material_ids=material_id, fields=["dos"])): + else: + if not ( + bs_doc := es_rester.search(material_ids=material_id, fields=["dos"]) + ): raise MPRestError("No electronic structure data found.") if (bs_data := bs_doc[0]["dos"]) is None: raise MPRestError( f"No uniform band structure data found for {material_id}" ) - + bs_data: dict = ( bs_data.model_dump() if self.use_document_model else bs_data # type: ignore ) @@ -446,9 +447,11 @@ def get_dos_from_material_id(self, material_id: str): if not (dos_data := dos_doc[0].get("dos")): raise MPRestError(f"No density of states data found for {material_id}") - - dos_task_id = (dos_data.model_dump() if self.use_document_model else dos_data)["total"]["1"]["task_id"] - if (dos_obj := self.get_dos_from_task_id(dos_task_id)): + + dos_task_id = (dos_data.model_dump() if self.use_document_model else dos_data)[ + "total" + ]["1"]["task_id"] + if dos_obj := self.get_dos_from_task_id(dos_task_id): return dos_obj - + raise MPRestError("No density of states object found.") diff --git a/tests/test_mprester.py b/tests/test_mprester.py index 6b2955e3..dbb5e395 100644 --- a/tests/test_mprester.py +++ b/tests/test_mprester.py @@ -149,7 +149,10 @@ def test_get_entries(self, mpr): # Conventional structure entry = next( - e for e in mpr.get_entry_by_material_id("mp-22526", conventional_unit_cell=True) + e + for e in mpr.get_entry_by_material_id( + "mp-22526", conventional_unit_cell=True + ) if e.entry_id == "mp-22526-r2SCAN" ) @@ -162,7 +165,10 @@ def test_get_entries(self, mpr): # Ensure energy per atom is same entry = next( - e for e in mpr.get_entry_by_material_id("mp-22526", conventional_unit_cell=False) + e + for e in mpr.get_entry_by_material_id( + "mp-22526", conventional_unit_cell=False + ) if e.entry_id == "mp-22526-r2SCAN" ) s = entry.structure From a6bd682719061b3b6832bef69f640c7210248575 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Tue, 21 Oct 2025 17:18:31 -0700 Subject: [PATCH 6/8] remove attempt at field validation - not working dynamically --- mp_api/client/core/client.py | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/mp_api/client/core/client.py b/mp_api/client/core/client.py index 69eea260..793d0902 100644 --- a/mp_api/client/core/client.py +++ b/mp_api/client/core/client.py @@ -18,12 +18,7 @@ from importlib.metadata import PackageNotFoundError, version from json import JSONDecodeError from math import ceil -from typing import ( - TYPE_CHECKING, - ForwardRef, - Optional, - get_args, -) +from typing import TYPE_CHECKING, ForwardRef, Optional, get_args from urllib.parse import quote, urljoin import requests @@ -1087,23 +1082,6 @@ def _generate_returned_model( field_copy, ) - validators = {} - for k in ( - "field_validators", - "model_validators", - "root_validators", - "validators", - ): - validators.update( - { - k: v.func - for k, v in getattr( - self.document_model.__pydantic_decorators__, k, {} - ).items() - if hasattr(v, "func") - } - ) - data_model = create_model( # type: ignore "MPDataDoc", **include_fields, From edfe222c424444bc876de4cc9ab7be2d96fc6f1c Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Fri, 24 Oct 2025 10:15:44 -0700 Subject: [PATCH 7/8] bump emmet to new rc --- pyproject.toml | 2 +- requirements/requirements-ubuntu-latest_py3.11.txt | 2 +- requirements/requirements-ubuntu-latest_py3.12.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f202666c..afefc1e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ dependencies = [ "typing-extensions>=3.7.4.1", "requests>=2.23.0", "monty>=2024.12.10", - "emmet-core>=0.85.1rc0,<0.86", + "emmet-core>=0.85.1rc0", "smart_open", "boto3", "orjson >= 3.10,<4", diff --git a/requirements/requirements-ubuntu-latest_py3.11.txt b/requirements/requirements-ubuntu-latest_py3.11.txt index 780c5ceb..c993b2f5 100644 --- a/requirements/requirements-ubuntu-latest_py3.11.txt +++ b/requirements/requirements-ubuntu-latest_py3.11.txt @@ -24,7 +24,7 @@ contourpy==1.3.3 # via matplotlib cycler==0.12.1 # via matplotlib -emmet-core==0.85.1 +emmet-core==0.86.0rc1 # via mp-api (pyproject.toml) fonttools==4.60.1 # via matplotlib diff --git a/requirements/requirements-ubuntu-latest_py3.12.txt b/requirements/requirements-ubuntu-latest_py3.12.txt index 67ad81fc..1b70c23f 100644 --- a/requirements/requirements-ubuntu-latest_py3.12.txt +++ b/requirements/requirements-ubuntu-latest_py3.12.txt @@ -24,7 +24,7 @@ contourpy==1.3.3 # via matplotlib cycler==0.12.1 # via matplotlib -emmet-core==0.85.1 +emmet-core==0.86.0rc1 # via mp-api (pyproject.toml) fonttools==4.60.1 # via matplotlib From 089c15712f5d2bf5b0599f8326f20f0c3ca572f0 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Fri, 24 Oct 2025 11:48:39 -0700 Subject: [PATCH 8/8] general cleanup --- mp_api/client/routes/materials/materials.py | 7 ++----- mp_api/client/routes/molecules/molecules.py | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/mp_api/client/routes/materials/materials.py b/mp_api/client/routes/materials/materials.py index 9d54f619..6b35fabd 100644 --- a/mp_api/client/routes/materials/materials.py +++ b/mp_api/client/routes/materials/materials.py @@ -126,7 +126,7 @@ def get_structure_by_material_id( response = self.search(material_ids=material_id, fields=[field]) - if response: + if response and response[0]: response = response[0] # Ensure that return type is a Structure regardless of `monty_decode` or `model_dump` output if isinstance(response[field], dict): @@ -312,7 +312,4 @@ def find_structure( ) return results # type: ignore - if results: - return results[0]["material_id"] - else: - return [] + return results[0]["material_id"] if (results and results[0]) else [] diff --git a/mp_api/client/routes/molecules/molecules.py b/mp_api/client/routes/molecules/molecules.py index 5553a526..4493932e 100644 --- a/mp_api/client/routes/molecules/molecules.py +++ b/mp_api/client/routes/molecules/molecules.py @@ -34,7 +34,7 @@ def get_molecule_by_mpculeid( field = "molecule" if final else "initial_molecules" response = self.search(molecule_ids=[mpcule_id], fields=[field]) # type: ignore - return response[0][field] if response else response # type: ignore + return response[0][field] if (response and response[0]) else response # type: ignore def find_molecule( self, @@ -90,10 +90,7 @@ def find_molecule( ) return results # type: ignore - if results: - return results[0]["molecule_id"] - else: - return [] + return results[0]["molecule_id"] if (results and results[0]) else [] def search( self,