class DBOps(): """ Setup """ def __init__(self): # Connecting to MongoDB Atlas and connecting to Database self.client = MongoClient(url) self.db = self.client.get_database('wallwitness_db') # Getting Collections self.sessions = self.db.sessions self.workouts = self.db.workouts self.weights = self.db.weights self.prs = self.db.prs # Initializing personal cache self.cache = Cache() """ Functions """ # Adding Documents def add_session(self, type, envr, avGr, hiGr, date, durn, locn, note): """ Insert new session data into the sessions collection. For rope climbing grades: a=.00,b=.25,c=.50,d=.75 Args: type (string): type of climbing ['boulder', 'toprope', 'sport'], envr (string): environment ['in', 'out'], avGr (float): average grade of session, hiGr (float): highest grade of session, date (datetime.datetime): date for entry, durn (float): length of session, locn (string): location of session, note (string): notes of session. Returns: no return. """ # Marshall data type_i = str(type) envr_i = str(envr) avGr_i = float(avGr) hiGr_i = float(hiGr) date_i = date durn_i = float(durn) locn_i = str(locn) note_i = str(note) to_insert = { 'type': type_i, 'envr': envr_i, 'avGr': avGr_i, 'hiGr': hiGr_i, 'date': date_i, 'durn': durn_i, 'locn': locn_i, 'note': note_i } # Insert it self.sessions.insert_one(to_insert) # Check if it is a new pr self.check_new_pr(type_i, hiGr_i, date) # Clear relevant cache data that is now dirty self.cache.clear_cache_by_type(type_i) def add_workout(self, type, date, sets, reps, avWt, hiWt): """ Insert new workout data into the workouts collection. Args: type (string): type of workout ['bench', 'neg', 'pistol'], date (datetime.datetime): date for entry, sets (int): sets of exercise, reps (float): reps for each set of exercise, avWt (float): weight average, hiWt (float): weight maximum. Returns: no return. """ # Marshall data type_i = str(type) date_i = date sets_i = int(sets) reps_i = float(reps) avWt_i = float(avWt) hiWt_i = float(hiWt) to_insert = { 'type': type_i, 'date': date_i, 'sets': sets_i, 'reps': reps_i, 'avWt': avWt_i, 'hiWt': hiWt_i } # Insert it self.workouts.insert_one(to_insert) # Check if it is a new pr self.check_new_pr(type_i, hiWt_i, date) # Clear relevant cache data that is now dirty self.cache.clear_cache_by_type(type_i) def add_weight(self, date, wght): """ Insert new weight data into the weights collection. Args: date (datetime.datetime): date for entry, wght (float): weight for entry. Returns: no return. """ # Marshall data date_i = date wght_i = float(wght) to_insert = {'date': date_i, 'wght': wght_i} # Insert it self.weights.insert_one(to_insert) # Check if it is a new pr self.check_new_pr('wght', wght_i, date) # Clear relevant cache data that is now dirty self.cache.clear_cache_by_type('wght') # Updating Documents def check_new_pr(self, type, new_data, date): """ Checks if new entry sets a new pr, and if it does updates it. Args: type (string): type of pr ['boulder', 'toprope', 'sport', 'bench', 'neg', 'pistol', 'wght'], new_data (float): new grade/weight pr, date (datetime.datetime): date of new pr. Returns: no return. """ # Get the current pr record of the given type curr_pr = self.prs.find_one({'type': type}) curr_rd = curr_pr['rcrd'] # Compare it to the new weight/grade if new_data > curr_rd: self.update_pr(type, new_data, date) def update_pr(self, type, rcrd, date): """ Updates correct pr with new date and record. Args: type (string): type of pr ['boulder', 'toprope', 'sport', 'bench', 'neg', 'pistol', 'weight'], rcrd (float): new grade/weight pr, date (datetime.datetime): date pr was attained. Returns: no return. """ # Marshall data date_i = date to_update = {'type': type, 'rcrd': rcrd, 'date': date_i} # Update the correct pr to the new data self.prs.update({'type': type}, to_update) # Querying Data def get_data_points(self, start, end, ax_option): """ Gets type data between start and end dates. Args: start (datetime.datetime): earliest date to query from, end (datetime.datetime): latest date to query from, ax_option (string): axis option to query [constants.marshalled_graph_ax_options.values()]. Returns: ([datetime.datetime], [float]) for graphing. """ # Check to use cache, then only query what's needed cache_start, cache_end = self.cache.get_date_range_cached(ax_option) # If new query is not adjacent to cached data, clear the cache if (cache_end != None and cache_end < start) or (cache_start != None and end < cache_start): self.cache.clear_cache_by_ax_option(ax_option) if cache_start == None and cache_end == None: # No cached data db_query_start = start db_query_end = end status = constants.NO_CACHE elif cache_start <= start and cache_end >= end: # All needed data is cached status = constants.ALL_IN_CACHE elif cache_start <= start and cache_end < end: # Need to query 'later' than cache db_query_start = cache_end + timedelta(days=1) db_query_end = end status = constants.LATER_THAN_CACHE elif cache_start > start and cache_end >= end: # Need to query 'earlier' than cache db_query_start = start db_query_end = cache_start - timedelta(days=1) status = constants.EARLIER_THAN_CACHE elif cache_start > start and cache_end < end: # Need to query both 'earlier' and 'later' than cache left_db_query_start = start left_db_query_end = cache_start - timedelta(days=1) right_db_query_start = cache_end + timedelta(days=1) right_db_query_end = end status = constants.SURROUND_CACHE # Perform query specific by ax_option if ax_option[:2] == 'SB': col = self.sessions type = 'boulder' elif ax_option[:2] == 'ST': col = self.sessions type = 'toprope' elif ax_option[:2] == 'SS': col = self.sessions type = 'sport' elif ax_option[:2] == 'WB': col = self.workouts type = 'bench' elif ax_option[:2] == 'WO': col = self.workouts type = 'neg' elif ax_option[:2] == 'WP': col = self.workouts type = 'pistol' else: col = self.weights type = None # Key for type of data to query key = ax_option[2:] # Case where one database query is needed if status == constants.NO_CACHE or status == constants.EARLIER_THAN_CACHE or status == constants.LATER_THAN_CACHE: # Session or workout if type != None: cursor = col.find({ 'date': { '$gte': db_query_start, '$lte': db_query_end }, 'type': { '$eq': type } }) # Body weight else: cursor = col.find( {'date': { '$gte': db_query_start, '$lte': db_query_end }}) # Format results db_x, db_y = self.parse_cursor(cursor, key) # Add new data to cache self.cache.add_data_to_cache(ax_option, db_x, db_y, db_query_start, db_query_end) # Case where two database queries are needed elif status == constants.SURROUND_CACHE: # Session or workout if type != None: left_cursor = col.find({ 'date': { '$gte': left_db_query_start, '$lte': left_db_query_end }, 'type': { '$eq': type } }) right_cursor = col.find({ 'date': { '$gte': right_db_query_start, '$lte': right_db_query_end }, 'type': { '$eq': type } }) # Body weight else: left_cursor = col.find({ 'date': { '$gte': left_db_query_start, '$lte': left_db_query_end } }) right_cursor = col.find({ 'date': { '$gte': right_db_query_start, '$lte': right_db_query_end } }) # Format results l_db_x, l_db_y = self.parse_cursor(left_cursor, key) r_db_x, r_db_y = self.parse_cursor(right_cursor, key) # Add new data to cache self.cache.add_data_to_cache(ax_option, l_db_x, l_db_y, left_db_query_start, left_db_query_end) self.cache.add_data_to_cache(ax_option, r_db_x, r_db_y, right_db_query_start, right_db_query_end) # Now that everything is cached, get all data from the cache quickly (O(N)) final_x, final_y = self.cache.query_data_from_cache( ax_option, start, end) # Return final result return final_x, final_y def parse_cursor(self, cursor, key): """ Gets data from cursor using key and returns tuple of lists. Args: cursor (Cursor): cursor to results from db query, key (string): four-character key for what data to get. Returns: ([datetime.datetime], [float]) for caching and graphing. """ # Create list of tuples to sort list_of_tuples = [] for doc in cursor: date = doc['date'] data = doc[key] list_of_tuples.append((date, data)) # Sort list based on date element in tuples sorted_list = sorted(list_of_tuples, key=lambda tup: tup[0]) # Split into lists to return date_list = [] data_list = [] for (date, data) in sorted_list: date_list.append(date) data_list.append(data) return date_list, data_list