From 9093f74223c97ac8b54f57752a9d97f207f94fed Mon Sep 17 00:00:00 2001 From: Jason Munro Date: Tue, 8 Aug 2023 12:19:32 -0700 Subject: [PATCH 1/2] Nest all resters --- mp_api/client/mprester.py | 260 ++++++++++++++++++-------------------- 1 file changed, 122 insertions(+), 138 deletions(-) diff --git a/mp_api/client/mprester.py b/mp_api/client/mprester.py index e08c0e1d..e705f12f 100644 --- a/mp_api/client/mprester.py +++ b/mp_api/client/mprester.py @@ -43,7 +43,6 @@ GrainBoundaryRester, MagnetismRester, MaterialsRester, - MoleculesRester, OxidationStatesRester, PhononRester, PiezoRester, @@ -111,7 +110,6 @@ class MPRester: magnetism: MagnetismRester summary: SummaryRester robocrys: RobocrysRester - molecules: MoleculesRester synthesis: SynthesisRester insertion_electrodes: ElectrodeRester charge_density: ChargeDensityRester @@ -226,9 +224,7 @@ def __init__( self.contribs = None warnings.warn(f"Problem loading MPContribs client: {error}") - if version.parse(emmet_version.base_version) < version.parse( - _MAPI_SETTINGS.MIN_EMMET_VERSION - ): + if version.parse(emmet_version.base_version) < version.parse(_MAPI_SETTINGS.MIN_EMMET_VERSION): warnings.warn( "The installed version of the mp-api client may not be compatible with the API server. " "Please install a previous version if any problems occur." @@ -242,12 +238,34 @@ def __init__( if not self.endpoint.endswith("/"): self.endpoint += "/" + # Set rester attributes + resters = [] + for _cls in BaseRester.__subclasses__(): sub_resters = _cls.__subclasses__() + if sub_resters: + resters.extend(sub_resters) + else: + resters.append(_cls) + + core_suffix = ["molecules/core", "materials/core"] + + core_resters = { + cls.suffix.split("/")[0]: cls( + api_key=api_key, + endpoint=endpoint, + include_user_agent=include_user_agent, + session=self.session, + monty_decode=monty_decode, + use_document_model=use_document_model, + headers=self.headers, + ) + for cls in resters + if cls.suffix in core_suffix + } - resters = sub_resters if sub_resters else [_cls] - - for cls in resters: + for cls in resters: + if cls.suffix not in core_suffix: rester = cls( api_key=api_key, endpoint=endpoint, @@ -264,8 +282,6 @@ def __init__( suffix_split = cls.suffix.split("/") - att_map = {"molecules/core": "molecules", "materials/core": "materials"} - if len(suffix_split) == 1: setattr( self, @@ -273,18 +289,26 @@ def __init__( rester, ) else: - if cls.suffix in att_map: - attr = att_map[cls.suffix] - elif "materials" in suffix_split: - attr = "_".join(suffix_split[1:]) - else: - attr = "_".join(suffix_split) + attr = "_".join(suffix_split[1:]) + if "materials" in suffix_split: + setattr( + core_resters["materials"], + attr, + rester, + ) + elif "molecules" in suffix_split or "jcesr" in suffix_split: + setattr( + core_resters["molecules"], + attr, + rester, + ) - setattr( - self, - attr, - rester, - ) + for attr, rester in core_resters.items(): + setattr( + self, + attr, + rester, + ) def __enter__(self): """Support for "with" context.""" @@ -295,12 +319,51 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.session.close() def __getattribute__(self, attr): + _deprecated_attributes = [ + "eos", + "similarity", + "tasks", + "xas", + "fermi", + "grain_boundary", + "substrates", + "surface_properties", + "phonon", + "elasticity", + "thermo", + "dielectric", + "piezoelectric", + "magnetism", + "summary", + "robocrys", + "synthesis", + "insertion_electrodes", + "charge_density", + "electronic_structure", + "electronic_structure_bandstructure", + "electronic_structure_dos", + "oxidation_states", + "provenance", + "bonds", + "alloys", + "absorption", + "chemenv", + ] + if "molecules" in attr: warnings.warn( "NOTE: You are accessing a new set of molecules data to be officially released very soon. " "This dataset includes many new properties, and is designed to be more easily expanded. " - "For the previous (legacy) molecules data, use the MPRester.legacy_jcesr rester. " + "For the previous (legacy) molecules data, use the MPRester.molecules.jcesr rester. " ) + elif attr in _deprecated_attributes: + warnings.warn( + f"Accessing {attr} data through MPRester.{attr} is deprecated. " + f"Please use MPRester.materials.{attr} instead.", + DeprecationWarning, + stacklevel=2, + ) + return super().__getattribute__("materials").__getattribute__(attr) return super().__getattribute__(attr) def __getattr__(self, attr): @@ -311,13 +374,11 @@ def __getattr__(self, attr): ) elif attr == "charge_density": raise MPRestError( - "boto3 not installed. " - "To query charge density data first install with: 'pip install boto3'" + "boto3 not installed. " "To query charge density data first install with: 'pip install boto3'" ) + else: - raise AttributeError( - f"{self.__class__.__name__!r} object has no attribute {attr!r}" - ) + raise AttributeError(f"{self.__class__.__name__!r} object has no attribute {attr!r}") def get_task_ids_associated_with_material_id( self, material_id: str, calc_types: Optional[List[CalcType]] = None @@ -326,13 +387,9 @@ def get_task_ids_associated_with_material_id( :param calc_types: if specified, will restrict to certain task types, e.g. [CalcType.GGA_STATIC] :return: """ - tasks = self.materials.get_data_by_id( - material_id, fields=["calc_types"] - ).calc_types + tasks = self.materials.get_data_by_id(material_id, fields=["calc_types"]).calc_types if calc_types: - return [ - task for task, calc_type in tasks.items() if calc_type in calc_types - ] + return [task for task, calc_type in tasks.items() if calc_type in calc_types] else: return list(tasks.keys()) @@ -352,19 +409,14 @@ def get_structure_by_material_id( Returns: Structure object or list of Structure objects. """ - structure_data = self.materials.get_structure_by_material_id( - material_id=material_id, final=final - ) + structure_data = self.materials.get_structure_by_material_id(material_id=material_id, final=final) if conventional_unit_cell and structure_data: if final: - structure_data = SpacegroupAnalyzer( - structure_data - ).get_conventional_standard_structure() + structure_data = SpacegroupAnalyzer(structure_data).get_conventional_standard_structure() else: structure_data = [ - SpacegroupAnalyzer(structure).get_conventional_standard_structure() - for structure in structure_data + SpacegroupAnalyzer(structure).get_conventional_standard_structure() for structure in structure_data ] return structure_data @@ -417,9 +469,7 @@ def get_material_id_from_task_id(self, task_id: str) -> Union[str, None]: if len(docs) == 1: # pragma: no cover return str(docs[0].material_id) # type: ignore elif len(docs) > 1: # pragma: no cover - raise ValueError( - f"Multiple documents return for {task_id}, this should not happen, please report it!" - ) + raise ValueError(f"Multiple documents return for {task_id}, this should not happen, please report it!") else: # pragma: no cover warnings.warn( f"No material found containing task {task_id}. Please report it if you suspect a task has gone missing." @@ -466,9 +516,7 @@ def get_material_ids( Returns: List of all materials ids ([MPID]) """ - if isinstance(chemsys_formula, list) or ( - isinstance(chemsys_formula, str) and "-" in chemsys_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} @@ -493,9 +541,7 @@ def get_materials_ids( ) return self.get_material_ids(chemsys_formula) - def get_structures( - self, chemsys_formula: Union[str, List[str]], final=True - ) -> List[Structure]: + def get_structures(self, chemsys_formula: Union[str, List[str]], final=True) -> List[Structure]: """Get a list of Structures corresponding to a chemical system or formula. Args: @@ -507,9 +553,7 @@ def get_structures( Returns: List of Structure objects. ([Structure]) """ - if isinstance(chemsys_formula, list) or ( - isinstance(chemsys_formula, str) and "-" in chemsys_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} @@ -658,11 +702,7 @@ def get_entries( ) for doc in docs: - entry_list = ( - doc.entries.values() - if self.use_document_model - else doc["entries"].values() - ) + entry_list = doc.entries.values() if self.use_document_model else doc["entries"].values() for entry in entry_list: entry_dict = entry.as_dict() if self.monty_decode else entry if not compatible_only: @@ -672,16 +712,12 @@ def get_entries( if property_data: for property in property_data: entry_dict["data"][property] = ( - doc.dict()[property] - if self.use_document_model - else doc[property] + doc.dict()[property] if self.use_document_model else doc[property] ) if conventional_unit_cell: entry_struct = Structure.from_dict(entry_dict["structure"]) - s = SpacegroupAnalyzer( - entry_struct - ).get_conventional_standard_structure() + s = SpacegroupAnalyzer(entry_struct).get_conventional_standard_structure() site_ratio = len(s) / len(entry_struct) new_energy = entry_dict["energy"] * site_ratio @@ -696,11 +732,7 @@ 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 - ) + entry = ComputedStructureEntry.from_dict(entry_dict) if self.monty_decode else entry_dict entries.append(entry) @@ -767,12 +799,8 @@ def get_pourbaix_entries( ion_data = self.get_ion_reference_data_for_chemsys(chemsys) # build the PhaseDiagram for get_ion_entries - ion_ref_comps = [ - Ion.from_formula(d["data"]["RefSolid"]).composition for d in ion_data - ] - ion_ref_elts = set( - itertools.chain.from_iterable(i.elements for i in ion_ref_comps) - ) + ion_ref_comps = [Ion.from_formula(d["data"]["RefSolid"]).composition for d in ion_data] + ion_ref_elts = set(itertools.chain.from_iterable(i.elements for i in ion_ref_comps)) # TODO - would be great if the commented line below would work # However for some reason you cannot process GibbsComputedStructureEntry with # MaterialsProjectAqueousCompatibility @@ -791,9 +819,7 @@ def get_pourbaix_entries( compat = MaterialsProjectAqueousCompatibility(solid_compat=solid_compat) # suppress the warning about missing oxidation states with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", message="Failed to guess oxidation states.*" - ) + warnings.filterwarnings("ignore", message="Failed to guess oxidation states.*") ion_ref_entries = compat.process_entries(ion_ref_entries) # TODO - if the commented line above would work, this conditional block # could be removed @@ -801,32 +827,21 @@ def get_pourbaix_entries( # replace the entries with GibbsComputedStructureEntry from pymatgen.entries.computed_entries import GibbsComputedStructureEntry - ion_ref_entries = GibbsComputedStructureEntry.from_entries( - ion_ref_entries, temp=use_gibbs - ) + ion_ref_entries = GibbsComputedStructureEntry.from_entries(ion_ref_entries, temp=use_gibbs) ion_ref_pd = PhaseDiagram(ion_ref_entries) ion_entries = self.get_ion_entries(ion_ref_pd, ion_ref_data=ion_data) pbx_entries = [PourbaixEntry(e, f"ion-{n}") for n, e in enumerate(ion_entries)] # Construct the solid pourbaix entries from filtered ion_ref entries - extra_elts = ( - set(ion_ref_elts) - - {Element(s) for s in chemsys} - - {Element("H"), Element("O")} - ) + extra_elts = set(ion_ref_elts) - {Element(s) for s in chemsys} - {Element("H"), Element("O")} for entry in ion_ref_entries: entry_elts = set(entry.composition.elements) # Ensure no OH chemsys or extraneous elements from ion references - if not ( - entry_elts <= {Element("H"), Element("O")} - or extra_elts.intersection(entry_elts) - ): + if not (entry_elts <= {Element("H"), Element("O")} or extra_elts.intersection(entry_elts)): # Create new computed entry form_e = ion_ref_pd.get_form_energy(entry) - new_entry = ComputedEntry( - entry.composition, form_e, entry_id=entry.entry_id - ) + new_entry = ComputedEntry(entry.composition, form_e, entry_id=entry.entry_id) pbx_entry = PourbaixEntry(new_entry) pbx_entries.append(pbx_entry) @@ -867,9 +882,7 @@ def get_ion_reference_data(self) -> List[Dict]: paginate=True, ).get("data") - def get_ion_reference_data_for_chemsys( - self, chemsys: Union[str, List] - ) -> List[Dict]: + def get_ion_reference_data_for_chemsys(self, chemsys: Union[str, List]) -> List[Dict]: """Download aqueous ion reference data used in the construction of Pourbaix diagrams. Use this method to examine the ion reference data and to add additional @@ -908,9 +921,7 @@ def get_ion_reference_data_for_chemsys( chemsys = chemsys.split("-") return [d for d in ion_data if d["data"]["MajElements"] in chemsys] - def get_ion_entries( - self, pd: PhaseDiagram, ion_ref_data: List[dict] = None - ) -> List[IonEntry]: + def get_ion_entries(self, pd: PhaseDiagram, ion_ref_data: List[dict] = None) -> List[IonEntry]: """Retrieve IonEntry objects that can be used in the construction of Pourbaix Diagrams. The energies of the IonEntry are calculaterd from the solid energies in the provided Phase Diagram to be @@ -943,8 +954,7 @@ def get_ion_entries( # raise ValueError if O and H not in chemsys if "O" not in chemsys or "H" not in chemsys: raise ValueError( - "The phase diagram chemical system must contain O and H! Your" - f" diagram chemical system is {chemsys}." + "The phase diagram chemical system must contain O and H! Your" f" diagram chemical system is {chemsys}." ) if not ion_ref_data: @@ -956,11 +966,7 @@ def get_ion_entries( ion_entries = [] for _n, i_d in enumerate(ion_data): ion = Ion.from_formula(i_d["formula"]) - refs = [ - e - for e in pd.all_entries - if e.composition.reduced_formula == i_d["data"]["RefSolid"] - ] + refs = [e for e in pd.all_entries if e.composition.reduced_formula == i_d["data"]["RefSolid"]] if not refs: raise ValueError("Reference solid not contained in entry list") stable_ref = sorted(refs, key=lambda x: x.energy_per_atom)[0] @@ -975,9 +981,7 @@ def get_ion_entries( # convert to eV/formula unit ref_solid_energy = i_d["data"]["ΔGᶠRefSolid"]["value"] / 96485 else: - raise ValueError( - f"Ion reference solid energy has incorrect unit {i_d['data']['ΔGᶠRefSolid']['unit']}" - ) + raise ValueError(f"Ion reference solid energy has incorrect unit {i_d['data']['ΔGᶠRefSolid']['unit']}") solid_diff = pd.get_form_energy(stable_ref) - ref_solid_energy * rf elt = i_d["data"]["MajElements"] correction_factor = ion.composition[elt] / stable_ref.composition[elt] @@ -990,9 +994,7 @@ def get_ion_entries( # convert to eV/formula unit ion_free_energy = i_d["data"]["ΔGᶠ"]["value"] / 96485 else: - raise ValueError( - f"Ion free energy has incorrect unit {i_d['data']['ΔGᶠ']['unit']}" - ) + raise ValueError(f"Ion free energy has incorrect unit {i_d['data']['ΔGᶠ']['unit']}") energy = ion_free_energy + solid_diff * correction_factor ion_entries.append(IonEntry(ion, energy)) @@ -1111,8 +1113,7 @@ def get_entries_in_chemsys( inc_structure=inc_structure, property_data=property_data, conventional_unit_cell=conventional_unit_cell, - additional_criteria=additional_criteria - or {"thermo_types": ["GGA_GGA+U"]}, + additional_criteria=additional_criteria or {"thermo_types": ["GGA_GGA+U"]}, ) ) @@ -1214,12 +1215,8 @@ def get_wulff_shape(self, material_id: str): from pymatgen.symmetry.analyzer import SpacegroupAnalyzer structure = self.get_structure_by_material_id(material_id) - surfaces = surfaces = self.surface_properties.get_data_by_id( - material_id - ).surfaces - lattice = ( - SpacegroupAnalyzer(structure).get_conventional_standard_structure().lattice - ) + surfaces = surfaces = self.surface_properties.get_data_by_id(material_id).surfaces + lattice = SpacegroupAnalyzer(structure).get_conventional_standard_structure().lattice miller_energy_map = {} for surf in surfaces: miller = tuple(surf.miller_index) @@ -1229,9 +1226,7 @@ def get_wulff_shape(self, material_id: str): millers, energies = zip(*miller_energy_map.items()) return WulffShape(lattice, millers, energies) - def get_charge_density_from_material_id( - self, material_id: str, inc_task_doc: bool = False - ) -> Optional[Chgcar]: + def get_charge_density_from_material_id(self, material_id: str, inc_task_doc: bool = False) -> Optional[Chgcar]: """Get charge density data for a given Materials Project ID. Arguments: @@ -1242,10 +1237,7 @@ def get_charge_density_from_material_id( chgcar: Pymatgen Chgcar object. """ if not hasattr(self, "charge_density"): - raise MPRestError( - "boto3 not installed. " - "To query charge density data install the boto3 package." - ) + raise MPRestError("boto3 not installed. " "To query charge density data install the boto3 package.") # TODO: really we want a recommended task_id for charge densities here # this could potentially introduce an ambiguity @@ -1292,11 +1284,7 @@ def get_download_info(self, material_ids, calc_types=None, file_patterns=None): metadata info, e.g. the task/external_ids that belong to a directory. """ # task_id's correspond to NoMaD external_id's - calc_types = ( - [t.value for t in calc_types if isinstance(t, CalcType)] - if calc_types - else [] - ) + calc_types = [t.value for t in calc_types if isinstance(t, CalcType)] if calc_types else [] meta = {} for doc in self.materials.search( @@ -1326,13 +1314,9 @@ def get_download_info(self, material_ids, calc_types=None, file_patterns=None): prefix += "external_id=" task_ids = [t["task_id"] for tl in meta.values() for t in tl] - nomad_exist_task_ids = self._check_get_download_info_url_by_task_id( - prefix=prefix, task_ids=task_ids - ) + nomad_exist_task_ids = self._check_get_download_info_url_by_task_id(prefix=prefix, task_ids=task_ids) if len(nomad_exist_task_ids) != len(task_ids): - self._print_help_message( - nomad_exist_task_ids, task_ids, file_patterns, calc_types - ) + self._print_help_message(nomad_exist_task_ids, task_ids, file_patterns, calc_types) # generate download links for those that exist prefix = "https://nomad-lab.eu/prod/rae/api/raw/query?" From a6ade57c2e20dd019ad0cb3533af7ed3c4ea3003 Mon Sep 17 00:00:00 2001 From: Jason Munro Date: Tue, 8 Aug 2023 14:24:39 -0700 Subject: [PATCH 2/2] Linting --- mp_api/client/mprester.py | 158 +++++++++++++++----- mp_api/client/routes/_user_settings.py | 8 +- mp_api/client/routes/materials/molecules.py | 2 +- 3 files changed, 125 insertions(+), 43 deletions(-) diff --git a/mp_api/client/mprester.py b/mp_api/client/mprester.py index e705f12f..ab22a021 100644 --- a/mp_api/client/mprester.py +++ b/mp_api/client/mprester.py @@ -224,7 +224,9 @@ def __init__( self.contribs = None warnings.warn(f"Problem loading MPContribs client: {error}") - if version.parse(emmet_version.base_version) < version.parse(_MAPI_SETTINGS.MIN_EMMET_VERSION): + if version.parse(emmet_version.base_version) < version.parse( + _MAPI_SETTINGS.MIN_EMMET_VERSION + ): warnings.warn( "The installed version of the mp-api client may not be compatible with the API server. " "Please install a previous version if any problems occur." @@ -296,7 +298,7 @@ def __init__( attr, rester, ) - elif "molecules" in suffix_split or "jcesr" in suffix_split: + elif "molecules" in suffix_split: setattr( core_resters["molecules"], attr, @@ -374,11 +376,14 @@ def __getattr__(self, attr): ) elif attr == "charge_density": raise MPRestError( - "boto3 not installed. " "To query charge density data first install with: 'pip install boto3'" + "boto3 not installed. " + "To query charge density data first install with: 'pip install boto3'" ) else: - raise AttributeError(f"{self.__class__.__name__!r} object has no attribute {attr!r}") + raise AttributeError( + f"{self.__class__.__name__!r} object has no attribute {attr!r}" + ) def get_task_ids_associated_with_material_id( self, material_id: str, calc_types: Optional[List[CalcType]] = None @@ -387,9 +392,13 @@ def get_task_ids_associated_with_material_id( :param calc_types: if specified, will restrict to certain task types, e.g. [CalcType.GGA_STATIC] :return: """ - tasks = self.materials.get_data_by_id(material_id, fields=["calc_types"]).calc_types + tasks = self.materials.get_data_by_id( + material_id, fields=["calc_types"] + ).calc_types if calc_types: - return [task for task, calc_type in tasks.items() if calc_type in calc_types] + return [ + task for task, calc_type in tasks.items() if calc_type in calc_types + ] else: return list(tasks.keys()) @@ -409,14 +418,19 @@ def get_structure_by_material_id( Returns: Structure object or list of Structure objects. """ - structure_data = self.materials.get_structure_by_material_id(material_id=material_id, final=final) + structure_data = self.materials.get_structure_by_material_id( + material_id=material_id, final=final + ) if conventional_unit_cell and structure_data: if final: - structure_data = SpacegroupAnalyzer(structure_data).get_conventional_standard_structure() + structure_data = SpacegroupAnalyzer( + structure_data + ).get_conventional_standard_structure() else: structure_data = [ - SpacegroupAnalyzer(structure).get_conventional_standard_structure() for structure in structure_data + SpacegroupAnalyzer(structure).get_conventional_standard_structure() + for structure in structure_data ] return structure_data @@ -469,7 +483,9 @@ def get_material_id_from_task_id(self, task_id: str) -> Union[str, None]: if len(docs) == 1: # pragma: no cover return str(docs[0].material_id) # type: ignore elif len(docs) > 1: # pragma: no cover - raise ValueError(f"Multiple documents return for {task_id}, this should not happen, please report it!") + raise ValueError( + f"Multiple documents return for {task_id}, this should not happen, please report it!" + ) else: # pragma: no cover warnings.warn( f"No material found containing task {task_id}. Please report it if you suspect a task has gone missing." @@ -516,7 +532,9 @@ def get_material_ids( Returns: List of all materials ids ([MPID]) """ - if isinstance(chemsys_formula, list) or (isinstance(chemsys_formula, str) and "-" in chemsys_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} @@ -541,7 +559,9 @@ def get_materials_ids( ) return self.get_material_ids(chemsys_formula) - def get_structures(self, chemsys_formula: Union[str, List[str]], final=True) -> List[Structure]: + def get_structures( + self, chemsys_formula: Union[str, List[str]], final=True + ) -> List[Structure]: """Get a list of Structures corresponding to a chemical system or formula. Args: @@ -553,7 +573,9 @@ def get_structures(self, chemsys_formula: Union[str, List[str]], final=True) -> Returns: List of Structure objects. ([Structure]) """ - if isinstance(chemsys_formula, list) or (isinstance(chemsys_formula, str) and "-" in chemsys_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} @@ -702,7 +724,11 @@ def get_entries( ) for doc in docs: - entry_list = doc.entries.values() if self.use_document_model else doc["entries"].values() + entry_list = ( + doc.entries.values() + if self.use_document_model + else doc["entries"].values() + ) for entry in entry_list: entry_dict = entry.as_dict() if self.monty_decode else entry if not compatible_only: @@ -712,12 +738,16 @@ def get_entries( if property_data: for property in property_data: entry_dict["data"][property] = ( - doc.dict()[property] if self.use_document_model else doc[property] + doc.dict()[property] + if self.use_document_model + else doc[property] ) if conventional_unit_cell: entry_struct = Structure.from_dict(entry_dict["structure"]) - s = SpacegroupAnalyzer(entry_struct).get_conventional_standard_structure() + s = SpacegroupAnalyzer( + entry_struct + ).get_conventional_standard_structure() site_ratio = len(s) / len(entry_struct) new_energy = entry_dict["energy"] * site_ratio @@ -732,7 +762,11 @@ 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 + entry = ( + ComputedStructureEntry.from_dict(entry_dict) + if self.monty_decode + else entry_dict + ) entries.append(entry) @@ -799,8 +833,12 @@ def get_pourbaix_entries( ion_data = self.get_ion_reference_data_for_chemsys(chemsys) # build the PhaseDiagram for get_ion_entries - ion_ref_comps = [Ion.from_formula(d["data"]["RefSolid"]).composition for d in ion_data] - ion_ref_elts = set(itertools.chain.from_iterable(i.elements for i in ion_ref_comps)) + ion_ref_comps = [ + Ion.from_formula(d["data"]["RefSolid"]).composition for d in ion_data + ] + ion_ref_elts = set( + itertools.chain.from_iterable(i.elements for i in ion_ref_comps) + ) # TODO - would be great if the commented line below would work # However for some reason you cannot process GibbsComputedStructureEntry with # MaterialsProjectAqueousCompatibility @@ -819,7 +857,9 @@ def get_pourbaix_entries( compat = MaterialsProjectAqueousCompatibility(solid_compat=solid_compat) # suppress the warning about missing oxidation states with warnings.catch_warnings(): - warnings.filterwarnings("ignore", message="Failed to guess oxidation states.*") + warnings.filterwarnings( + "ignore", message="Failed to guess oxidation states.*" + ) ion_ref_entries = compat.process_entries(ion_ref_entries) # TODO - if the commented line above would work, this conditional block # could be removed @@ -827,21 +867,32 @@ def get_pourbaix_entries( # replace the entries with GibbsComputedStructureEntry from pymatgen.entries.computed_entries import GibbsComputedStructureEntry - ion_ref_entries = GibbsComputedStructureEntry.from_entries(ion_ref_entries, temp=use_gibbs) + ion_ref_entries = GibbsComputedStructureEntry.from_entries( + ion_ref_entries, temp=use_gibbs + ) ion_ref_pd = PhaseDiagram(ion_ref_entries) ion_entries = self.get_ion_entries(ion_ref_pd, ion_ref_data=ion_data) pbx_entries = [PourbaixEntry(e, f"ion-{n}") for n, e in enumerate(ion_entries)] # Construct the solid pourbaix entries from filtered ion_ref entries - extra_elts = set(ion_ref_elts) - {Element(s) for s in chemsys} - {Element("H"), Element("O")} + extra_elts = ( + set(ion_ref_elts) + - {Element(s) for s in chemsys} + - {Element("H"), Element("O")} + ) for entry in ion_ref_entries: entry_elts = set(entry.composition.elements) # Ensure no OH chemsys or extraneous elements from ion references - if not (entry_elts <= {Element("H"), Element("O")} or extra_elts.intersection(entry_elts)): + if not ( + entry_elts <= {Element("H"), Element("O")} + or extra_elts.intersection(entry_elts) + ): # Create new computed entry form_e = ion_ref_pd.get_form_energy(entry) - new_entry = ComputedEntry(entry.composition, form_e, entry_id=entry.entry_id) + new_entry = ComputedEntry( + entry.composition, form_e, entry_id=entry.entry_id + ) pbx_entry = PourbaixEntry(new_entry) pbx_entries.append(pbx_entry) @@ -882,7 +933,9 @@ def get_ion_reference_data(self) -> List[Dict]: paginate=True, ).get("data") - def get_ion_reference_data_for_chemsys(self, chemsys: Union[str, List]) -> List[Dict]: + def get_ion_reference_data_for_chemsys( + self, chemsys: Union[str, List] + ) -> List[Dict]: """Download aqueous ion reference data used in the construction of Pourbaix diagrams. Use this method to examine the ion reference data and to add additional @@ -921,7 +974,9 @@ def get_ion_reference_data_for_chemsys(self, chemsys: Union[str, List]) -> List[ chemsys = chemsys.split("-") return [d for d in ion_data if d["data"]["MajElements"] in chemsys] - def get_ion_entries(self, pd: PhaseDiagram, ion_ref_data: List[dict] = None) -> List[IonEntry]: + def get_ion_entries( + self, pd: PhaseDiagram, ion_ref_data: List[dict] = None + ) -> List[IonEntry]: """Retrieve IonEntry objects that can be used in the construction of Pourbaix Diagrams. The energies of the IonEntry are calculaterd from the solid energies in the provided Phase Diagram to be @@ -954,7 +1009,8 @@ def get_ion_entries(self, pd: PhaseDiagram, ion_ref_data: List[dict] = None) -> # raise ValueError if O and H not in chemsys if "O" not in chemsys or "H" not in chemsys: raise ValueError( - "The phase diagram chemical system must contain O and H! Your" f" diagram chemical system is {chemsys}." + "The phase diagram chemical system must contain O and H! Your" + f" diagram chemical system is {chemsys}." ) if not ion_ref_data: @@ -966,7 +1022,11 @@ def get_ion_entries(self, pd: PhaseDiagram, ion_ref_data: List[dict] = None) -> ion_entries = [] for _n, i_d in enumerate(ion_data): ion = Ion.from_formula(i_d["formula"]) - refs = [e for e in pd.all_entries if e.composition.reduced_formula == i_d["data"]["RefSolid"]] + refs = [ + e + for e in pd.all_entries + if e.composition.reduced_formula == i_d["data"]["RefSolid"] + ] if not refs: raise ValueError("Reference solid not contained in entry list") stable_ref = sorted(refs, key=lambda x: x.energy_per_atom)[0] @@ -981,7 +1041,9 @@ def get_ion_entries(self, pd: PhaseDiagram, ion_ref_data: List[dict] = None) -> # convert to eV/formula unit ref_solid_energy = i_d["data"]["ΔGᶠRefSolid"]["value"] / 96485 else: - raise ValueError(f"Ion reference solid energy has incorrect unit {i_d['data']['ΔGᶠRefSolid']['unit']}") + raise ValueError( + f"Ion reference solid energy has incorrect unit {i_d['data']['ΔGᶠRefSolid']['unit']}" + ) solid_diff = pd.get_form_energy(stable_ref) - ref_solid_energy * rf elt = i_d["data"]["MajElements"] correction_factor = ion.composition[elt] / stable_ref.composition[elt] @@ -994,7 +1056,9 @@ def get_ion_entries(self, pd: PhaseDiagram, ion_ref_data: List[dict] = None) -> # convert to eV/formula unit ion_free_energy = i_d["data"]["ΔGᶠ"]["value"] / 96485 else: - raise ValueError(f"Ion free energy has incorrect unit {i_d['data']['ΔGᶠ']['unit']}") + raise ValueError( + f"Ion free energy has incorrect unit {i_d['data']['ΔGᶠ']['unit']}" + ) energy = ion_free_energy + solid_diff * correction_factor ion_entries.append(IonEntry(ion, energy)) @@ -1113,7 +1177,8 @@ def get_entries_in_chemsys( inc_structure=inc_structure, property_data=property_data, conventional_unit_cell=conventional_unit_cell, - additional_criteria=additional_criteria or {"thermo_types": ["GGA_GGA+U"]}, + additional_criteria=additional_criteria + or {"thermo_types": ["GGA_GGA+U"]}, ) ) @@ -1215,8 +1280,12 @@ def get_wulff_shape(self, material_id: str): from pymatgen.symmetry.analyzer import SpacegroupAnalyzer structure = self.get_structure_by_material_id(material_id) - surfaces = surfaces = self.surface_properties.get_data_by_id(material_id).surfaces - lattice = SpacegroupAnalyzer(structure).get_conventional_standard_structure().lattice + surfaces = surfaces = self.surface_properties.get_data_by_id( + material_id + ).surfaces + lattice = ( + SpacegroupAnalyzer(structure).get_conventional_standard_structure().lattice + ) miller_energy_map = {} for surf in surfaces: miller = tuple(surf.miller_index) @@ -1226,7 +1295,9 @@ def get_wulff_shape(self, material_id: str): millers, energies = zip(*miller_energy_map.items()) return WulffShape(lattice, millers, energies) - def get_charge_density_from_material_id(self, material_id: str, inc_task_doc: bool = False) -> Optional[Chgcar]: + def get_charge_density_from_material_id( + self, material_id: str, inc_task_doc: bool = False + ) -> Optional[Chgcar]: """Get charge density data for a given Materials Project ID. Arguments: @@ -1237,7 +1308,10 @@ def get_charge_density_from_material_id(self, material_id: str, inc_task_doc: bo chgcar: Pymatgen Chgcar object. """ if not hasattr(self, "charge_density"): - raise MPRestError("boto3 not installed. " "To query charge density data install the boto3 package.") + raise MPRestError( + "boto3 not installed. " + "To query charge density data install the boto3 package." + ) # TODO: really we want a recommended task_id for charge densities here # this could potentially introduce an ambiguity @@ -1284,7 +1358,11 @@ def get_download_info(self, material_ids, calc_types=None, file_patterns=None): metadata info, e.g. the task/external_ids that belong to a directory. """ # task_id's correspond to NoMaD external_id's - calc_types = [t.value for t in calc_types if isinstance(t, CalcType)] if calc_types else [] + calc_types = ( + [t.value for t in calc_types if isinstance(t, CalcType)] + if calc_types + else [] + ) meta = {} for doc in self.materials.search( @@ -1314,9 +1392,13 @@ def get_download_info(self, material_ids, calc_types=None, file_patterns=None): prefix += "external_id=" task_ids = [t["task_id"] for tl in meta.values() for t in tl] - nomad_exist_task_ids = self._check_get_download_info_url_by_task_id(prefix=prefix, task_ids=task_ids) + nomad_exist_task_ids = self._check_get_download_info_url_by_task_id( + prefix=prefix, task_ids=task_ids + ) if len(nomad_exist_task_ids) != len(task_ids): - self._print_help_message(nomad_exist_task_ids, task_ids, file_patterns, calc_types) + self._print_help_message( + nomad_exist_task_ids, task_ids, file_patterns, calc_types + ) # generate download links for those that exist prefix = "https://nomad-lab.eu/prod/rae/api/raw/query?" diff --git a/mp_api/client/routes/_user_settings.py b/mp_api/client/routes/_user_settings.py index dd8f4698..862f54ef 100644 --- a/mp_api/client/routes/_user_settings.py +++ b/mp_api/client/routes/_user_settings.py @@ -35,10 +35,10 @@ def set_user_settings(self, consumer_id, settings): # pragma: no cover f"Invalid setting key {key}. Must be one of institution, sector, job_role, is_email_subscribed" ) body[f"settings.{key}"] = settings[key] - - return self._patch_resource( - body=body, params={"consumer_id": consumer_id} - ).get("data") + + return self._patch_resource(body=body, params={"consumer_id": consumer_id}).get( + "data" + ) def patch_user_time_settings(self, consumer_id, time): # pragma: no cover """Set user settings. diff --git a/mp_api/client/routes/materials/molecules.py b/mp_api/client/routes/materials/molecules.py index acd0b00f..062952e1 100644 --- a/mp_api/client/routes/materials/molecules.py +++ b/mp_api/client/routes/materials/molecules.py @@ -9,7 +9,7 @@ class MoleculesRester(BaseRester[MoleculesDoc]): - suffix = "legacy/jcesr" + suffix = "molecules/jcesr" document_model = MoleculesDoc # type: ignore primary_key = "task_id"