def update_targets(self, items: List[List[Dict]]): """ Inserts the new task_types into the task_types collection Args: items ([([dict],[int])]): A list of tuples of materials to update and the corresponding processed task_ids """ items = list(filter(None, chain.from_iterable(items))) for item in items: item.update({"_bt": self.timestamp}) material_ids = list({item["material_id"] for item in items}) if len(items) > 0: self.logger.info(f"Updating {len(items)} materials") self.materials.remove_docs({self.materials.key: {"$in": material_ids}}) self.materials.update( docs=jsanitize(items, allow_bson=True), key=["material_id"], ) else: self.logger.info("No items to update")
def update_targets(self, items): """ Inserts the thermo docs into the thermo collection Args: items ([[dict]]): a list of list of thermo dictionaries to update """ # flatten out lists items = list(filter(None, chain.from_iterable(items))) # Check if already updated this run items = [ i for i in items if i[self.thermo.key] not in self._completed_tasks ] self._completed_tasks |= {i[self.thermo.key] for i in items} for item in items: if isinstance(item["last_updated"], dict): item["last_updated"] = MontyDecoder().process_decoded( item["last_updated"]) if len(items) > 0: self.logger.info(f"Updating {len(items)} thermo documents") self.thermo.update(docs=jsanitize(items), key=[self.thermo.key]) else: self.logger.info("No items to update")
def process_item(self, item) -> Dict: """ - Add volume information to each entry to create the insertion electrode document - Add the host structure """ if item is None: return None self.logger.debug( f"Working on {item['group_id']} with {len(item['thermo_docs'])}") entries = [ tdoc_["entries"][tdoc_["energy_type"]] for tdoc_ in item["thermo_docs"] ] entries = list(map(ComputedStructureEntry.from_dict, entries)) working_ion_entry = ComputedEntry.from_dict( item["working_ion_doc"]["entries"][item["working_ion_doc"] ["energy_type"]]) working_ion = working_ion_entry.composition.reduced_formula decomp_energies = { d_["material_id"]: d_["energy_above_hull"] for d_ in item["thermo_docs"] } least_wion_ent = min( entries, key=lambda x: x.composition.get_atomic_fraction(working_ion)) host_structure = least_wion_ent.structure.copy() host_structure.remove_species([item["working_ion"]]) for ient in entries: ient.data["volume"] = ient.structure.volume ient.data["decomposition_energy"] = decomp_energies[ient.entry_id] ie = InsertionElectrodeDoc.from_entries( grouped_entries=entries, working_ion_entry=working_ion_entry, battery_id=item["group_id"], host_structure=host_structure, ) if ie is None: return None # {"failed_reason": "unable to create InsertionElectrode document"} return jsanitize(ie.dict())
def test_jsanitize(): """ Tests emmet Jsanitize which converts MSONable classes into dicts """ # clean_json should have no effect on None types. d = {"hello": 1, "world": None} clean = jsanitize(d) assert clean["world"] is None assert json.loads(json.dumps(d)) == json.loads(json.dumps(clean)) d = {"hello": GoodMSONClass(1, 2, 3)} with pytest.raises(TypeError): json.dumps(d) clean = jsanitize(d) assert isinstance(clean["hello"], dict) clean_strict = jsanitize(d, strict=True) assert clean_strict["hello"]["a"] == 1 assert clean_strict["hello"]["b"] == 2 d = {"dt": datetime.datetime.now()} clean = jsanitize(d) assert isinstance(clean["dt"], str) clean = jsanitize(d, allow_bson=True) assert isinstance(clean["dt"], datetime.datetime) d = { "a": ["b", np.array([1, 2, 3])], "b": ObjectId.from_datetime(datetime.datetime.now()), } clean = jsanitize(d) assert clean["a"] == ["b", [1, 2, 3]] assert isinstance(clean["b"], str) rnd_bin = bytes(np.random.rand(10)) d = {"a": bytes(rnd_bin)} clean = jsanitize(d, allow_bson=True) assert clean["a"] == bytes(rnd_bin) assert isinstance(clean["a"], bytes)
def _post_resource( self, body: Dict = None, params: Optional[Dict] = None, monty_decode: bool = True, suburl: Optional[str] = None, use_document_model: Optional[bool] = True, ): """ Post data to the endpoint for a Resource. Arguments: body: body json to send in post request params: extra params to send in post request monty_decode: Decode the data using monty into python objects suburl: make a request to a specified sub-url use_document_model: whether to use the core document model for data reconstruction Returns: A Resource, a dict with two keys, "data" containing a list of documents, and "meta" containing meta information, e.g. total number of documents available. """ check_limit() payload = jsanitize(body) try: url = self.endpoint if suburl: url = urljoin(self.endpoint, suburl) if not url.endswith("/"): url += "/" response = self.session.post(url, json=payload, verify=True, params=params) if response.status_code == 200: if monty_decode: data = json.loads(response.text, cls=MontyDecoder) else: data = json.loads(response.text) if self.document_model and use_document_model: data["data"] = [ self.document_model.parse_obj(d) for d in data["data"] ] # type: ignore return data else: try: data = json.loads(response.text)["detail"] except (JSONDecodeError, KeyError): data = "Response {}".format(response.text) if isinstance(data, str): message = data else: try: message = ", ".join( "{} - {}".format(entry["loc"][1], entry["msg"]) for entry in data) except (KeyError, IndexError): message = str(data) raise MPRestError( f"REST post query returned with error status code {response.status_code} " f"on URL {response.url} with message:\n{message}") except RequestException as ex: raise MPRestError(str(ex))