def get(self): live_events = EventHelper.getEventsWithinADay() for event in live_events: taskqueue.add(url='/tasks/math/do/predict_match_times/{}'.format( event.key_name), method='GET') # taskqueue.add(url='/tasks/do/bluezone_update', method='GET') # Clear down events for events that aren't live status_sitevar = Sitevar.get_by_id('apistatus.down_events') if status_sitevar is not None: live_event_keys = set([e.key.id() for e in live_events]) old_status = set(status_sitevar.contents) new_status = old_status.copy() for event_key in old_status: if event_key not in live_event_keys: new_status.remove(event_key) status_sitevar.contents = list(new_status) status_sitevar.put() # Clear API Response cache ApiStatusController.clear_cache_if_needed(old_status, new_status) self.response.out.write( "Enqueued time prediction for {} events".format(len(live_events)))
def post(self): self._require_admin() trusted_sitevar = Sitevar.get_or_insert('trustedapi') sitevar = Sitevar.get_or_insert('apistatus') old_value = sitevar.contents status = {} status['android'] = {} status['ios'] = {} status['max_season'] = int(self.request.get('max_year')) status['current_season'] = int(self.request.get('current_year')) status['android']['latest_app_version'] = int(self.request.get('android_latest_version')) status['android']['min_app_version'] = int(self.request.get('android_min_version')) status['ios']['latest_app_version'] = int(self.request.get('ios_latest_version')) status['ios']['min_app_version'] = int(self.request.get('ios_min_version')) sitevar.contents = status sitevar.put() trusted_status = { 1: True if self.request.get('enable_match_video') else False, 2: True if self.request.get('enable_event_teams') else False, 3: True if self.request.get('enable_event_matches') else False, 4: True if self.request.get('enable_event_rankings') else False, 5: True if self.request.get('enable_event_alliances') else False, 6: True if self.request.get('enable_event_awards') else False, } trusted_sitevar.contents = trusted_status trusted_sitevar.put() ApiStatusController.clear_cache_if_needed(old_value, status) self.redirect('/admin/apistatus')
def send_upcoming_matches(cls, live_events): from helpers.match_helper import MatchHelper # PJL: Hacky :P # Causes circular import, otherwise # https://github.com/the-blue-alliance/the-blue-alliance/pull/1098#discussion_r25128966 down_events = [] now = datetime.datetime.utcnow() for event in live_events: matches = event.matches if not matches: continue last_matches = MatchHelper.recentMatches(matches, num=1) next_matches = MatchHelper.upcomingMatches(matches, num=2) # First, compare the difference between scheduled times of next/last match # Send an upcoming notification if it's <10 minutes, to account for events ahead of schedule if last_matches != []: last_match = last_matches[0] for i, next_match in enumerate(next_matches): if not next_match.push_sent and last_match.time and next_match.time: diff = next_match.time - last_match.time if diff < datetime.timedelta(minutes=10 * (i + 1)): cls.send_upcoming_match_notification( next_match, event) for match in next_matches: if match and not match.push_sent: # Only continue sending for the next match if a push hasn't already been sent for it if match.time is None or match.time + datetime.timedelta( minutes=-7) <= now: # Only send notifications for matches no more than 7 minutes (average-ish match cycle time) before it's scheduled to start # Unless, the match has no time info. Then #yolo and send it cls.send_upcoming_match_notification(match, event) # Determine if event is down if cls.is_event_down(last_matches[0] if last_matches else None, next_matches[0] if next_matches else None): down_events.append(event.key_name) # Update the status sitevar status_sitevar = Sitevar.get_by_id('apistatus.down_events') if status_sitevar is None: status_sitevar = Sitevar(id="apistatus.down_events", description="A list of down event keys", values_json="[]") old_status = status_sitevar.contents status_sitevar.contents = down_events status_sitevar.put() # Clear API Response cache ApiStatusController.clear_cache_if_needed(old_status, down_events)
def get(self, event_key): import pytz event = Event.get_by_id(event_key) if not event: self.abort(404) matches = event.matches if not matches or not event.timezone_id: return timezone = pytz.timezone(event.timezone_id) played_matches = MatchHelper.recentMatches(matches, num=0) unplayed_matches = MatchHelper.upcomingMatches(matches, num=len(matches)) MatchTimePredictionHelper.predict_future_matches( event_key, played_matches, unplayed_matches, timezone, event.within_a_day) # Detect whether the event is down # An event NOT down if ANY unplayed match's predicted time is within its scheduled time by a threshold and # the last played match (if it exists) wasn't too long ago. event_down = len(unplayed_matches) > 0 for unplayed_match in unplayed_matches: if ((unplayed_match.predicted_time and unplayed_match.time and unplayed_match.predicted_time < unplayed_match.time + datetime.timedelta(minutes=30)) or (played_matches == [] or played_matches[-1].actual_time is None or played_matches[-1].actual_time > datetime.datetime.now() - datetime.timedelta(minutes=30))): event_down = False break status_sitevar = Sitevar.get_by_id('apistatus.down_events') if status_sitevar is None: status_sitevar = Sitevar(id="apistatus.down_events", description="A list of down event keys", values_json="[]") old_status = set(status_sitevar.contents) new_status = old_status.copy() if event_down: new_status.add(event_key) elif event_key in new_status: new_status.remove(event_key) status_sitevar.contents = list(new_status) status_sitevar.put() # Clear API Response cache ApiStatusController.clear_cache_if_needed(old_status, new_status)
def send_upcoming_matches(cls, live_events): from helpers.match_helper import MatchHelper # PJL: Hacky :P # Causes circular import, otherwise # https://github.com/the-blue-alliance/the-blue-alliance/pull/1098#discussion_r25128966 down_events = [] now = datetime.datetime.utcnow() for event in live_events: matches = event.matches if not matches: continue last_matches = MatchHelper.recentMatches(matches, num=1) next_matches = MatchHelper.upcomingMatches(matches, num=2) # First, compare the difference between scheduled times of next/last match # Send an upcoming notification if it's <10 minutes, to account for events ahead of schedule if last_matches != []: last_match = last_matches[0] for i, next_match in enumerate(next_matches): if not next_match.push_sent and last_match.time and next_match.time: diff = next_match.time - last_match.time if diff < datetime.timedelta(minutes=10 * (i + 1)): cls.send_upcoming_match_notification(next_match, event) for match in next_matches: if match and not match.push_sent: # Only continue sending for the next match if a push hasn't already been sent for it if match.time is None or match.time + datetime.timedelta(minutes=-7) <= now: # Only send notifications for matches no more than 7 minutes (average-ish match cycle time) before it's scheduled to start # Unless, the match has no time info. Then #yolo and send it cls.send_upcoming_match_notification(match, event) # Determine if event is down if cls.is_event_down(last_matches[0] if last_matches else None, next_matches[0] if next_matches else None): down_events.append(event.key_name) # Update the status sitevar status_sitevar = Sitevar.get_by_id('apistatus.down_events') if status_sitevar is None: status_sitevar = Sitevar(id="apistatus.down_events", description="A list of down event keys", values_json="[]") old_status = status_sitevar.contents status_sitevar.contents = down_events status_sitevar.put() # Clear API Response cache ApiStatusController.clear_cache_if_needed(old_status, down_events)
def post(self, sitevar_key): self._require_admin() # note, we don't use sitevar_key sitevar = Sitevar( id=self.request.get("key"), description=self.request.get("description"), values_json=self.request.get("values_json"), ) sitevar.put() # If we're changing an apistatus sitevar, clear the API response cache # Since this will be used rarely, always clear the cache on update if 'apistatus' in self.request.get("key"): ApiStatusController.clear_cache_if_needed(False, True) self.redirect("/admin/sitevar/edit/" + sitevar.key.id() + "?success=true")
def get(self, event_key): import pytz event = Event.get_by_id(event_key) if not event: self.abort(404) matches = event.matches if not matches or not event.timezone_id: return timezone = pytz.timezone(event.timezone_id) played_matches = MatchHelper.recentMatches(matches, num=0) unplayed_matches = MatchHelper.upcomingMatches(matches, num=len(matches)) MatchTimePredictionHelper.predict_future_matches(event_key, played_matches, unplayed_matches, timezone, event.within_a_day) # Detect whether the event is down # An event NOT down if ANY unplayed match's predicted time is within its scheduled time by a threshold and # the last played match (if it exists) wasn't too long ago. event_down = len(unplayed_matches) > 0 for unplayed_match in unplayed_matches: if ((unplayed_match.predicted_time and unplayed_match.time and unplayed_match.predicted_time < unplayed_match.time + datetime.timedelta(minutes=30)) or (played_matches == [] or played_matches[-1].actual_time is None or played_matches[-1].actual_time > datetime.datetime.now() - datetime.timedelta(minutes=30))): event_down = False break status_sitevar = Sitevar.get_by_id('apistatus.down_events') if status_sitevar is None: status_sitevar = Sitevar(id="apistatus.down_events", description="A list of down event keys", values_json="[]") old_status = set(status_sitevar.contents) new_status = old_status.copy() if event_down: new_status.add(event_key) elif event_key in new_status: new_status.remove(event_key) status_sitevar.contents = list(new_status) status_sitevar.put() # Clear API Response cache ApiStatusController.clear_cache_if_needed(old_status, new_status)
def post(self): self._require_admin() sitevar = Sitevar.get_or_insert('apistatus') old_value = sitevar.contents status = {} status['android'] = {} status['ios'] = {} status['max_season'] = int(self.request.get('max_year')) status['android']['latest_app_version'] = int(self.request.get('android_latest_version')) status['android']['min_app_version'] = int(self.request.get('android_min_version')) status['ios']['latest_app_version'] = int(self.request.get('ios_latest_version')) status['ios']['min_app_version'] = int(self.request.get('ios_min_version')) sitevar.contents = status sitevar.put() ApiStatusController.clear_cache_if_needed(old_value, status) self.redirect('/admin/apistatus')
def post(self): self._require_admin() trusted_sitevar = Sitevar.get_or_insert('trustedapi') sitevar = Sitevar.get_or_insert('apistatus') old_value = sitevar.contents status = {} status['android'] = {} status['ios'] = {} status['max_season'] = int(self.request.get('max_year')) status['current_season'] = int(self.request.get('current_year')) status['android']['latest_app_version'] = int( self.request.get('android_latest_version')) status['android']['min_app_version'] = int( self.request.get('android_min_version')) status['ios']['latest_app_version'] = int( self.request.get('ios_latest_version')) status['ios']['min_app_version'] = int( self.request.get('ios_min_version')) sitevar.contents = status sitevar.put() trusted_status = { 1: True if self.request.get('enable_match_video') else False, 2: True if self.request.get('enable_event_teams') else False, 3: True if self.request.get('enable_event_matches') else False, 4: True if self.request.get('enable_event_rankings') else False, 5: True if self.request.get('enable_event_alliances') else False, 6: True if self.request.get('enable_event_awards') else False, } trusted_sitevar.contents = trusted_status trusted_sitevar.put() ApiStatusController.clear_cache_if_needed(old_value, status) self.redirect('/admin/apistatus')
def get(self): live_events = EventHelper.getEventsWithinADay() for event in live_events: taskqueue.add(url='/tasks/math/do/predict_match_times/{}'.format(event.key_name), method='GET') # taskqueue.add(url='/tasks/do/bluezone_update', method='GET') # Clear down events for events that aren't live status_sitevar = Sitevar.get_by_id('apistatus.down_events') if status_sitevar is not None: live_event_keys = set([e.key.id() for e in live_events]) old_status = set(status_sitevar.contents) new_status = old_status.copy() for event_key in old_status: if event_key not in live_event_keys: new_status.remove(event_key) status_sitevar.contents = list(new_status) status_sitevar.put() # Clear API Response cache ApiStatusController.clear_cache_if_needed(old_status, new_status) self.response.out.write("Enqueued time prediction for {} events".format(len(live_events)))
'Cache-Control': 'no-cache, max-age=10', 'Pragma': 'no-cache', } try: result = yield context.urlfetch(url, headers=headers) except Exception, e: logging.error("URLFetch failed for: {}".format(url)) logging.info(e) raise ndb.Return(None) old_status = self._is_down_sitevar.contents if result.status_code == 200: if old_status == True: self._is_down_sitevar.contents = False self._is_down_sitevar.put() ApiStatusController.clear_cache_if_needed(old_status, self._is_down_sitevar.contents) # Save raw API response into cloudstorage if self._save_response and tba_config.CONFIG['save-frc-api-response']: try: # Check for last response last_item = None for last_item in gcs_dir_contents: pass write_new = True if last_item is not None: with cloudstorage.open(last_item.filename, 'r') as last_json_file: if last_json_file.read() == result.content: write_new = False # Do not write if content didn't change
class DatafeedFMSAPI(object): EVENT_SHORT_EXCEPTIONS = { 'arc': 'archimedes', 'cars': 'carson', 'carv': 'carver', 'cur': 'curie', 'gal': 'galileo', 'hop': 'hopper', 'new': 'newton', 'tes': 'tesla', } SUBDIV_TO_DIV = { 'arc': 'cmp-arte', 'cars': 'cmp-gaca', 'carv': 'cmp-cuca', 'cur': 'cmp-cuca', 'gal': 'cmp-gaca', 'hop': 'cmp-neho', 'new': 'cmp-neho', 'tes': 'cmp-arte', } def __init__(self, version): fms_api_secrets = Sitevar.get_by_id('fmsapi.secrets') if fms_api_secrets is None: raise Exception( "Missing sitevar: fmsapi.secrets. Can't access FMS API.") fms_api_username = fms_api_secrets.contents['username'] fms_api_authkey = fms_api_secrets.contents['authkey'] self._fms_api_authtoken = base64.b64encode('{}:{}'.format( fms_api_username, fms_api_authkey)) self._is_down_sitevar = Sitevar.get_by_id('apistatus.fmsapi_down') if not self._is_down_sitevar: self._is_down_sitevar = Sitevar(id="apistatus.fmsapi_down", description="Is FMSAPI down?") if version == 'v1.0': FMS_API_URL_BASE = 'https://frc-api.firstinspires.org/api/v1.0' self.FMS_API_AWARDS_URL_PATTERN = FMS_API_URL_BASE + '/awards/%s/%s' # (year, event_short) self.FMS_API_HYBRID_SCHEDULE_QUAL_URL_PATTERN = FMS_API_URL_BASE + '/schedule/%s/%s/qual/hybrid' # (year, event_short) self.FMS_API_HYBRID_SCHEDULE_PLAYOFF_URL_PATTERN = FMS_API_URL_BASE + '/schedule/%s/%s/playoff/hybrid' # (year, event_short) self.FMS_API_EVENT_RANKINGS_URL_PATTERN = FMS_API_URL_BASE + '/rankings/%s/%s' # (year, event_short) self.FMS_API_EVENT_ALLIANCES_URL_PATTERN = FMS_API_URL_BASE + '/alliances/%s/%s' # (year, event_short) self.FMS_API_TEAM_DETAILS_URL_PATTERN = FMS_API_URL_BASE + '/teams/%s/?teamNumber=%s' # (year, teamNumber) self.FMS_API_EVENT_LIST_URL_PATTERN = FMS_API_URL_BASE + '/events/season=%s' self.FMS_API_EVENTTEAM_LIST_URL_PATTERN = FMS_API_URL_BASE + '/teams/?season=%s&eventCode=%s&page=%s' # (year, eventCode, page) elif version == 'v2.0': FMS_API_URL_BASE = 'https://frc-api.firstinspires.org/v2.0' self.FMS_API_AWARDS_URL_PATTERN = FMS_API_URL_BASE + '/%s/awards/%s' # (year, event_short) self.FMS_API_HYBRID_SCHEDULE_QUAL_URL_PATTERN = FMS_API_URL_BASE + '/%s/schedule/%s/qual/hybrid' # (year, event_short) self.FMS_API_HYBRID_SCHEDULE_PLAYOFF_URL_PATTERN = FMS_API_URL_BASE + '/%s/schedule/%s/playoff/hybrid' # (year, event_short) self.FMS_API_MATCH_DETAILS_QUAL_URL_PATTERN = FMS_API_URL_BASE + '/%s/scores/%s/qual' # (year, event_short) self.FMS_API_MATCH_DETAILS_PLAYOFF_URL_PATTERN = FMS_API_URL_BASE + '/%s/scores/%s/playoff' # (year, event_short) self.FMS_API_EVENT_RANKINGS_URL_PATTERN = FMS_API_URL_BASE + '/%s/rankings/%s' # (year, event_short) self.FMS_API_EVENT_ALLIANCES_URL_PATTERN = FMS_API_URL_BASE + '/%s/alliances/%s' # (year, event_short) self.FMS_API_TEAM_DETAILS_URL_PATTERN = FMS_API_URL_BASE + '/%s/teams/?teamNumber=%s' # (year, teamNumber) self.FMS_API_EVENT_LIST_URL_PATTERN = FMS_API_URL_BASE + '/%s/events' # year self.FMS_API_EVENTTEAM_LIST_URL_PATTERN = FMS_API_URL_BASE + '/%s/teams/?eventCode=%s&page=%s' # (year, eventCode, page) else: raise Exception("Unknown FMS API version: {}".format(version)) def _get_event_short(self, event_short): return self.EVENT_SHORT_EXCEPTIONS.get(event_short, event_short) @ndb.tasklet def _parse_async(self, url, parser): headers = { 'Authorization': 'Basic {}'.format(self._fms_api_authtoken), 'Cache-Control': 'no-cache, max-age=10', 'Pragma': 'no-cache', } try: rpc = urlfetch.create_rpc(deadline=10) result = yield urlfetch.make_fetch_call(rpc, url, headers=headers) except Exception, e: logging.error("URLFetch failed for: {}".format(url)) logging.info(e) raise ndb.Return(None) old_status = self._is_down_sitevar.contents if result.status_code == 200: self._is_down_sitevar.contents = False self._is_down_sitevar.put() ApiStatusController.clear_cache_if_needed( old_status, self._is_down_sitevar.contents) raise ndb.Return(parser.parse(json.loads(result.content))) elif result.status_code % 100 == 5: # 5XX error - something is wrong with the server logging.warning('URLFetch for %s failed; Error code %s' % (url, result.status_code)) self._is_down_sitevar.contents = True self._is_down_sitevar.put() ApiStatusController.clear_cache_if_needed( old_status, self._is_down_sitevar.contents) raise ndb.Return(None) else: logging.warning('URLFetch for %s failed; Error code %s' % (url, result.status_code)) raise ndb.Return(None)