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
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
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
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
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)
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
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]