コード例 #1
0
    def __init__(self, path=None, user=None):
        """ Initialize :class:`~ankipandas.collection.Collection` object.

        Args:
            path: (Search) path to database see
                :py:func:`~ankipandas.paths.db_path_input` for more
                information.
            user: Anki user name. See
                :py:func:`~ankipandas.paths.db_path_input` for more
                information.

        Examples:

        .. code-block:: python

            from ankipandas import Collection

            # Let ankipandas find the db for you
            col = Collection()

            # Let ankipandas find the db for this user (important if you have
            # more than one user account in Anki)
            col = Collection(user="******")

            # Specify full path to Anki's database
            col = Collection("/full/path/to/collection.anki2")

            # Speficy partial path to Anki's database and specify user
            col = Collection("/partial/path/to/collection", user="******")

        """
        path = ankipandas.paths.db_path_input(path, user=user)

        #: Path to currently loaded database
        self.path = path  # type: Path

        log.info("Loaded db from {}".format(self.path))

        #: Opened Anki database (:class:`sqlite3.Connection`)
        self.db = raw.load_db(self.path)  # type: sqlite3.Connection

        #: Should be accessed with _get_item!
        self.__items = {
            "notes": None,
            "cards": None,
            "revs": None,
        }  # type: Dict[str, AnkiDataFrame]

        #: Should be accessed with _get_original_item!
        self.__original_items = {
            "notes": None,
            "cards": None,
            "revs": None,
        }  # type: Dict[str, AnkiDataFrame]
コード例 #2
0
ファイル: paths.py プロジェクト: klieret/AnkiPandas
def db_path_input(path: Union[str, PurePath] = None, user: str = None) -> Path:
    """Helper function to interpret user input of path to database.

    1. If no path is given, we search through some default locations
    2. If path points to a file: Take that file
    3. If path points to a directory: Search in that directory

    Args:
        path: Path to database or search path or None
        user: User name of anki collection or None

    Returns:
        Path to anki database as :class:`Path` object

    Raises:
        If path does not exist: :class:`FileNotFoundError`
        In various other cases: :class:`ValueError`
    """
    if path is None:
        result = find_db(user=user)
    else:
        path = Path(path)
        if not path.exists():
            raise FileNotFoundError(
                "db_path_input: File '{}' does not exist.".format(str(path))
            )
        if path.is_file():
            log.debug(
                "db_path_input: Database explicitly set to %r.", str(path)
            )
            result = path
        else:
            result = find_db(
                search_paths=(path,), user=user, break_on_first=False
            )
            log.info("Database found at %r.", str(result))
    if result:
        return result
    else:
        raise ValueError("Database could not be found.")
コード例 #3
0
ファイル: paths.py プロジェクト: DexDex1515/AnkiPandas
def find_db(
    search_paths=None,
    maxdepth=8,
    filename="collection.anki2",
    user=None,
    break_on_first=True,
) -> pathlib.Path:
    """
    Find path to anki2 database.

    Args:
        search_paths: Search path as string or pathlib object or list/iterable
            thereof. If None, some search paths are set by default.
        maxdepth: Maximal search depth.
        filename: Filename of the collection (default: ``collections.anki2``)
        user: Username to which the collection belongs. If None, search for
            databases of any user.
        break_on_first: Stop searching once a database is found. This is
            obviously faster, but you will not get any errors if there are
            multiple databases matching your criteria.

    Raises:
        If none ore more than one result is found: :class:`ValueError`

    Returns:
        pathlib.Path to the anki2 database
    """
    if not search_paths:
        log.info("Searching for database. This might take some time. "
                 "You can speed this up by specifying a search path or "
                 "directly entering the path to your database.")
        search_paths = [
            "~/.local/share/Anki2/",
            "~/Documents/Anki2",
            pathlib.Path(os.getenv("APPDATA", "~") + "/Anki2/"),
            "~/.local/share/Anki2",
            pathlib.Path.home(),
        ]
        search_paths = [
            pathlib.Path(sp).expanduser().resolve() for sp in search_paths
        ]
    if break_on_first:
        log.warning(
            "The search will stop at the first hit, so please verify that "
            "the result is correct (for example in case there might be more "
            "than one Anki installation)")
    if isinstance(search_paths, (str, pathlib.PurePath)):
        search_paths = [search_paths]
    found = {}
    for search_path in search_paths:
        found = {
            **found,
            **_find_db(
                search_path,
                maxdepth=maxdepth,
                filename=filename,
                user=user,
                break_on_first=break_on_first,
            ),
        }
        if break_on_first:
            if user is not None:
                if user in found:
                    break
            else:
                if found:
                    break

    if user:
        if user not in found:
            raise ValueError(
                f"Could not find database belonging to user {user}")
        found = found[user]
    else:
        if len(found) >= 2:
            raise ValueError(
                "Found databases for more than one user: {}. Please specify "
                "the user.".format(", ".join(found)))
        elif not found:
            raise ValueError(
                "No database found. You might increase the search depth or "
                "specify search paths to find more.")
        else:
            found = found.popitem()[1]
    if len(found) >= 2:
        raise ValueError(
            "Found more than one database belonging to user {} at {}".format(
                user, ", ".join(map(str, found))))
    found = found[0]
    log.debug("Database found at %r.", found)
    return found
コード例 #4
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
コード例 #5
0
 def test_log(self):
     log.info("Test info")
     log.warning("Test warning")
コード例 #6
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)
            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