def test_update(self): notes2 = get_table(self.db_read, "notes") notes = get_table(self.db_read, "notes") for mode in ["update", "replace", "append"]: with self.subTest(mode=mode): self._reset() notes2.loc[notes2["id"] == 1555579337683, "tags"] = "definitelynew!" set_table(self.db_write, notes2, "notes", mode) if mode == "append": self._check_db_equal() else: notes2r = get_table(self.db_write, "notes") chtag = notes2r.loc[notes2r["id"] == 1555579337683, "tags"] self.assertListEqual(list(chtag.values.tolist()), ["definitelynew!"]) unchanged = notes.loc[notes["id"] != 1555579337683, :] unchanged2 = notes2r.loc[notes2["id"] != 1555579337683, :] self.assertListEqual( list(unchanged.values.tolist()), list(unchanged2.values.tolist()), )
def test_rw_identical(self): notes = get_table(self.db_read, "notes") cards = get_table(self.db_read, "cards") revlog = get_table(self.db_read, "revs") for mode in ["update", "replace", "append"]: with self.subTest(mode=mode): self._reset() set_table(self.db_write, notes, "notes", mode) set_table(self.db_write, cards, "cards", mode) set_table(self.db_write, revlog, "revs", mode) self._check_db_equal()
def test_update_append_does_not_delete(self): notes = get_table(self.db_read, "notes") cards = get_table(self.db_read, "cards") revs = get_table(self.db_read, "revs") notes.drop(notes.index) cards.drop(cards.index) revs.drop(revs.index) for mode in ["update", "append"]: with self.subTest(mode=mode): self._reset() set_table(self.db_write, notes, "notes", mode) set_table(self.db_write, cards, "cards", mode) set_table(self.db_write, revs, "revs", mode) self._check_db_equal()
def test_replace_deletes(self): notes = get_table(self.db_read, "notes") cards = get_table(self.db_read, "cards") revs = get_table(self.db_read, "revs") notes = notes.drop(notes.index) cards = cards.drop(cards.index) revs = revs.drop(revs.index) self._reset() set_table(self.db_write, notes, "notes", "replace") set_table(self.db_write, cards, "cards", "replace") set_table(self.db_write, revs, "revs", "replace") notes = get_table(self.db_write, "notes") cards = get_table(self.db_write, "cards") revs = get_table(self.db_write, "revs") self.assertEqual(len(notes), 0) self.assertEqual(len(revs), 0) self.assertEqual(len(cards), 0)
def write( self, modify=False, add=False, delete=False, backup_folder: Union[PurePath, str] = None, ): """ Creates a backup of the database and then writes back the new data. .. danger:: The switches ``modify``, ``add`` and ``delete`` will run additional cross-checks, but do not rely on them to 100%! .. warning:: It is recommended to run :meth:`summarize_changes` before to check whether the changes match your expectation. .. note:: Please make sure to thoroughly check your collection in Anki after every write process! Args: modify: Allow modification of existing items (notes, cards, etc.) add: Allow adding of new items (notes, cards, etc.) delete: Allow deletion of items (notes, cards, etc.) backup_folder: Path to backup folder. If None is given, the backup is created in the Anki backup directory (if found). Returns: None """ if not modify and not add and not delete: log.warning( "Please set modify=True, add=True or delete=True, you're" " literally not allowing me any modification at all." ) return None try: prepared = self._prepare_write_data( modify=modify, add=add, delete=delete ) info = self._get_and_update_info() except Exception as e: log.critical( "Something went wrong preparing the data for writing. " "However, no data has been written out, so your" "database is save!" ) raise e else: log.debug("Successfully prepared data for writing.") backup_path = ankipandas.paths.backup_db( self.path, backup_folder=backup_folder ) log.info("Backup created at {}.".format(backup_path.resolve())) # Actually setting values here, after all conversion tasks have been # carried out. That way if any of them fails, we don't have a # partially written collection. log.debug("Now actually writing to database.") try: for table, values in prepared.items(): raw.set_table( self.db, values["raw"], table=table, mode=values["mode"] ) # Actually only needed if we actually modify the info. # This will trigger a complete re-upload, so we want to avoid this # if possible. # raw.set_info(self.db, info) except Exception as e: log.critical( "Error while writing data to database at {path}" "This means that your database might have become corrupted. " "It's STRONGLY adviced that you manually restore the database " "by replacing it with the backup from {backup_path} and restart" " from scratch. " "Please also open a bug report at " "https://github.com/klieret/AnkiPandas/issues/, as errors " "during the actual writing process should never occurr!".format( path=self.path.resolve(), backup_path=backup_path.resolve() ) ) raise e
def write( self, modify=False, add=False, delete=False, backup_folder: Union[PurePath, str] = None, ): """ Creates a backup of the database and then writes back the new data. .. danger:: The switches ``modify``, ``add`` and ``delete`` will run additional cross-checks, but do not rely on them to 100%! .. warning:: It is recommended to run :meth:`summarize_changes` before to check whether the changes match your expectation. .. note:: Please make sure to thoroughly check your collection in Anki after every write process! Args: modify: Allow modification of existing items (notes, cards, etc.) add: Allow adding of new items (notes, cards, etc.) delete: Allow deletion of items (notes, cards, etc.) backup_folder: Path to backup folder. If None is given, the backup is created in the Anki backup directory (if found). Returns: None """ if not modify and not add and not delete: log.warning( "Please set modify=True, add=True or delete=True, you're" " literally not allowing me any modification at all.") return None try: prepared = self._prepare_write_data(modify=modify, add=add, delete=delete) log.debug("Now getting & updating info.") info = self._get_and_update_info() except Exception as e: log.critical( "Something went wrong preparing the data for writing. " "However, no data has been written out, so your " "database is save!") raise e else: log.debug("Successfully prepared data for writing.") if prepared == {}: log.warning( "Nothing seems to have been changed. Will not do anything!") return None backup_path = ankipandas.paths.backup_db(self.path, backup_folder=backup_folder) log.info(f"Backup created at {backup_path.resolve()}.") log.warning( "Currently AnkiPandas might not be able to tell Anki to" " sync its database. " "You might have to manually tell Anki to sync everything " "to AnkiDroid.\n" "Furthermore, if you run into issues with tag searches not working" "anymore, please first do Notes > Clear unused notes and then " "Tools > Check Database (from the main menu). This should get them" " to work (sorry about this issue).") # Actually setting values here, after all conversion tasks have been # carried out. That way if any of them fails, we don't have a # partially written collection. log.debug("Now actually writing to database.") try: for table, values in prepared.items(): log.debug(f"Now setting table {table}.") raw.set_table(self.db, values["raw"], table=table, mode=values["mode"]) log.debug(f"Setting table {table} successful.") # log.debug("Now setting info") # raw.set_info(self.db, info) # log.debug("Setting info successful.") except Exception as e: log.critical( "Error while writing data to database at {path}" "This means that your database might have become corrupted. " "It's STRONGLY advised that you manually restore the database " "by replacing it with the backup from {backup_path} and restart" " from scratch. " "Please also open a bug report at " "https://github.com/klieret/AnkiPandas/issues/, as errors " "during the actual writing process should never occur!".format( path=self.path.resolve(), backup_path=backup_path.resolve())) raise e