Example #1
0
def get_card_by_id(card_id, db_path=config.get_DB_name()):
    """
    Takes in:
    1. An integer that represents target card's id.
    2. A path to the DB file (optional, defaults to config.DB)

    Returns an object of type knards.Card representing the card data stored in
    the DB.
    """

    try:
        int(card_id)
    except ValueError:
        raise ValueError('id must be a proper number.')

    with util.db_connect(db_path) as connection:
        cursor = connection.cursor()
        cursor.execute("""
            SELECT * FROM cards WHERE id = {}
        """.format(card_id))
        card = cursor.fetchone()

    if not card:
        raise exceptions.CardNotFound(
            'Card #{} was not found in the DB.'.format(card_id))

    card_obj = knards.Card(*card)
    return card_obj
Example #2
0
def get_series_set(series_name, db_path=config.get_DB_name()):
    """Returns all cards that belong to the specified series.

    Args:
        series_name (str): The series name
        db_path (str): The path to the DB (optional, defaults to what's defined in
    config module)

    Raises:
        TODO

    Returns:
        A set of cards all of which belong to the series
    """

    if not isinstance(series_name, str):
        raise TypeError('\'series_name\' argument must be a list.')

    with util.db_connect(db_path) as connection:
        cursor = connection.cursor()
        cursor.execute("""
            SELECT * FROM cards WHERE series = '{}'
        """.format(series_name))
        card_set = cursor.fetchall()

    if not card_set:
        raise exceptions.EmptyDB(
            'No cards adhere to the specified constraints.')

    sorted_card_set = {}
    for card in card_set:
        sorted_card_set[knards.Card(*card).pos_in_series] = knards.Card(*card)

    return sorted_card_set
Example #3
0
def update_card(card_obj, update_now=True, db_path=config.get_DB_name()):
    """
    Takes in:
    1. An object of type knards.Card
    2. A path to the DB file (optional, defaults to config.DB)

    Returns the id of the card that is updated.
    """

    if type(card_obj) is not knards.Card:
        raise ValueError('Input card object must be of type knards.Card')

    with util.db_connect(db_path) as connection:
        cursor = connection.cursor()
        if update_now:
            cursor.execute(
                """
                UPDATE cards SET question = ?,
                                answer = ?,
                                markers = ?,
                                series = ?,
                                pos_in_series = ?,
                                date_updated = ?,
                                score = ?
                            WHERE id = ?
            """, (card_obj.question, card_obj.answer,
                  card_obj.markers, card_obj.series, card_obj.pos_in_series,
                  datetime.now(), card_obj.score, card_obj.id))
        else:
            cursor.execute(
                """
                UPDATE cards SET question = ?,
                                answer = ?,
                                markers = ?,
                                series = ?,
                                pos_in_series = ?,
                                score = ?
                            WHERE id = ?
            """, (card_obj.question, card_obj.answer, card_obj.markers,
                  card_obj.series, card_obj.pos_in_series, card_obj.score,
                  card_obj.id))

    return card_obj.id
Example #4
0
def create_card(card_obj, db_path=config.get_DB_name()):
    """
    Takes in:
    1. An object of type knards.Card
    2. A path to the DB file (optional, defaults to config.DB)

    Returns an id of the card in the DB created based on the passed in object.
    """

    if type(card_obj) is not knards.Card:
        raise ValueError('Input card object must be of type knards.Card')

    # find a free id
    # this allows to reuse ids that were used and then freed up by deleting the
    # object
    free_id = 1
    with util.db_connect(db_path) as connection:
        cursor = connection.cursor()
        cursor.execute("""
            SELECT (id) FROM cards
        """)

        for id in cursor.fetchall():
            if free_id != id[0]:
                break

            free_id += 1

        card_obj = card_obj._replace(id=free_id)

    with util.db_connect(db_path) as connection:
        cursor = connection.cursor()
        cursor.execute(
            """
            INSERT INTO cards VALUES ({})
        """.format(','.join(list('?' * len(card_obj)))), (card_obj))
        created_with_id = cursor.lastrowid

    return created_with_id
Example #5
0
def merge(db_file):
    """
    TODO
    """

    # check if merge file exists and is a proper DB file
    try:
        with util.db_connect(db_file) as connection:
            cursor = connection.cursor()
            cursor.execute("""
                SELECT * FROM cards
            """)
            card_set = cursor.fetchall()

        card_set_as_objects = []
        for card in card_set:
            card_set_as_objects.append(Card(*card))

        full_card_set = api.get_card_set()
        dates = []
        for card in full_card_set:
            dates.append(card.date_created)

    except exceptions.DBFileNotFound as e:
        print(e.args[0])
        sys.exit(1)
    except sqlite3.DatabaseError:
        print('{} is not a proper DB file to merge.'.format(db_file))
        sys.exit(1)

    # backup both DB files
    try:
        copyfile(
            config.get_DB_name(),
            config.get_backup_path() +
            'main_{}'.format(datetime.now().strftime('%Y_%m_%d_%H_%M_%S_%f')))
    except IOError as e:
        print("Unable to copy file. %s" % e)
        sys.exit(1)
    except:
        print("Unexpected error:", sys.exc_info())
        sys.exit(1)

    try:
        copyfile(
            db_file,
            config.get_backup_path() +
            'merge_{}'.format(datetime.now().strftime('%Y_%m_%d_%H_%M_%S_%f')))
    except IOError as e:
        print("Unable to copy file. %s" % e)
        sys.exit(1)
    except:
        print("Unexpected error:", sys.exc_info())
        sys.exit(1)

    # merge
    merged = 0
    skipped = 0
    for card in card_set_as_objects:
        if card.date_created in dates:
            skipped += 1
            continue

        merged += 1
        api.create_card(card)

    if skipped == 0:
        click.secho(
            'DB file ({}) was successfully merged into the default DB file.'.
            format(db_file),
            fg='green',
            bold=True)

        os.remove(db_file)
    else:
        click.secho(
            '{} cards merged; {} cards skipped; You might be trying to merge a DB \
      file that was already merged.\nMerged DB file wasn\'t removed in case \
      something went wrong, please check manually and then remove the file.'.
            format(merged, skipped),
            fg='red',
            bold=True)
Example #6
0
def get_card_set(revisable_only=False,
                 show_question=True,
                 show_answer=True,
                 include_markers=[],
                 exclude_markers=[],
                 today=False,
                 db_path=config.get_DB_name()):
    """Outputs a set of objects of type knards.Card constrained by the passed in
    options

    Args:
        revisable_only (bool): Returns only card objects that are ready to be
    revised
        show_questions (bool): Include/exclude question text into results
        show_answer (bool): Include/exclude answer text into results
        include_markers (str[]): A list of markers all of which each card that is
    to be revised must have
        exclude_markers (str[]): A list of markers none of which each card that is
    to be revised must have
        today (bool): Returns only card objects that were already revised today
        db_path (str): The path to the DB (optional, defaults to what's defined in
    config module)

    Raises:
        TODO

    Returns:
        TODO
    """

    if not isinstance(revisable_only, bool):
        raise TypeError('revisable_only must be a boolean.')
    if not isinstance(today, bool):
        raise TypeError('today must be a boolean.')
    if not isinstance(show_question, bool):
        raise TypeError('show_question must be a boolean.')
    if not isinstance(show_answer, bool):
        raise TypeError('show_answer must be a boolean.')
    if not isinstance(include_markers, abc.Sequence):
        raise TypeError('include_markers must be a list.')
    if not isinstance(exclude_markers, abc.Sequence):
        raise TypeError('exclude_markers must be a list.')

    with util.db_connect(db_path) as connection:
        cursor = connection.cursor()
        cursor.execute("""
            SELECT * FROM cards
        """)
        card_set = cursor.fetchall()

    if not card_set:
        raise exceptions.EmptyDB(
            'No cards adhere to the specified constraints.')

    card_set_as_objects = []
    for card in card_set:
        card_set_as_objects.append(knards.Card(*list(card)))

    card_set_revisable = []
    for card in card_set_as_objects:
        # if revisable_only is True -> only return cards that has their score
        # equal to or more than the difference between today and the date of the
        # last card update (revision) and cards that weren't yet revised
        if revisable_only:
            if card.date_updated is None:
                card_set_revisable.append(card)
            elif card.score <= (datetime.now() - card.date_updated).days:
                card_set_revisable.append(card)
        else:
            card_set_revisable = card_set_as_objects
            break

    card_set_included_markers = []
    for card in card_set_revisable:
        # if include_markers is not '' -> filter cards that must have all of the
        # specified markers (as whole words)
        if include_markers:
            compare_list = card.markers.split()
            for marker in include_markers:
                if not marker in compare_list:
                    break
            else:
                card_set_included_markers.append(card)
        else:
            card_set_included_markers = card_set_revisable
            break

    card_set_excluded_markers = []
    for card in card_set_included_markers:
        # if exclude_markers is not '' -> filter cards that must have none of the
        # specified markers (as whole words)
        if exclude_markers:
            compare_list = card.markers.split()
            for marker in exclude_markers:
                if marker in compare_list:
                    break
            else:
                card_set_excluded_markers.append(card)
        else:
            card_set_excluded_markers = card_set_included_markers
            break

    card_set_today = []
    for card in card_set_excluded_markers:
        # return cards that have date_updated equal to today's date (were revised
        # today)
        if today:
            if card.date_updated and card.date_updated.date() == datetime.now(
            ).date():
                card_set_today.append(card)
        else:
            card_set_today = card_set_excluded_markers
            break

    if not show_question:
        card_set_without_questions = []
        for card in card_set_today:
            card_set_without_questions.append(card._replace(question=''))
    else:
        card_set_without_questions = card_set_today

    if not show_answer:
        card_set_without_answers = []
        for card in card_set_without_questions:
            card_set_without_answers.append(card._replace(answer=''))
    else:
        card_set_without_answers = card_set_without_questions

    return card_set_without_answers
Example #7
0
def delete_card(card_id=None,
                markers=None,
                series=None,
                db_path=config.get_DB_name()):
    """Deletes a card/cards from the DB

    Args:
        card_id (int): The id of the card that is to be deleted
        markers (str[]): A list of markers all of which each card that is to be
    deleted must have
        series (str): The name of the series that is to be deleted
        db_path (str): The path to the DB (optional, defaults to what's defined in
    config module)

    Raises:
        TODO

    Returns:
        card_id: id of the card that was deleted
        deleted_ids: List of all deleted cards' ids
    """

    assert card_id or markers or series
    assert isinstance(db_path, str) and len(db_path) > 0

    if card_id:
        if not isinstance(get_card_by_id(card_id), knards.Card):
            raise exceptions.CardNotFound(
                'Card #{} does not exist in the DB.'.format(card_id))

        with util.db_connect(db_path) as connection:
            cursor = connection.cursor()
            cursor.execute("""
                DELETE FROM cards WHERE id = {}
            """.format(card_id))

        return card_id

    elif markers:
        if not isinstance(markers, list):
            raise TypeError('\'markers\' argument must be a list.')

        card_set = get_card_set(include_markers=markers)
        assert len(card_set) != len(get_card_set())

        with util.db_connect(db_path) as connection:
            cursor = connection.cursor()
            for card in card_set:
                cursor.execute("""
                    DELETE FROM cards WHERE id = {}
                """.format(card.id))

        return [card.id for card in card_set]

    elif series:
        if not isinstance(series, str):
            raise TypeError('\'series\' argument must be a string.')

        with util.db_connect(db_path) as connection:
            cursor = connection.cursor()
            cursor.execute("""
                SELECT * FROM cards WHERE series = '{}'
            """.format(series))
            card_set = cursor.fetchall()

            card_set_as_objects = []
            for card in card_set:
                card_set_as_objects.append(knards.Card(*list(card)))

            cursor.execute("""
                DELETE FROM cards WHERE series = '{}'
            """.format(series))

        return [card.id for card in card_set_as_objects]