class RestClient(RestClientInterface):
    '''Define concrete Adapter
    '''
    _name = 'provider1'
    _abiospool = Provider1Pool()

    def __init__(self, *args, **kwargs):
        cherrypy.thread_data.client_obj = self
        if 'log' in kwargs:
            self._log = kwargs['log']
        super().__init__(*args, **kwargs)


    def _logging(self, message: str, url: str = None, log_type: str = None, log_data: dict = {}) -> None:
        if self._log:
            api_config[self._name]['log'] = True
        if api_config[self._name]['log'] is False:
            return
        if not hasattr(self, 'logger'):
            self.logger = LibPool().logger
        logdata = log_data
        logdata = {
            'connection_details': api_config[self._name]
        }
        if url:
            logdata['url'] = url
        if log_type is not None and log_type == 'error':
            self.logger.error(message, logdata)
        else:
            self.logger.info(message, logdata)


    def send_request(self, api_endpoint: str, data: dict = {}) -> dict:
        auth_token = self._abiospool.auth_token
        if auth_token is None:
            # Auth token does not exists, get new one
            self.authenticate()
            auth_token = self._abiospool.auth_token
            if auth_token is None:
                error_message = 'Cannot connect to Provider 2'
                self._logging(error_message, log_type='error')
                raise cherrypy.HTTPError(401, error_message)

        ac = api_config[self._name]
        url_start = '{}{}?'.format(ac['url'], api_endpoint)
        if '?' in api_endpoint:
            url_start = '{}&'.format(url_start[0:-1])
        url = '{}access_token={}'.format(url_start, auth_token)
        if self._abiospool.request_permission:
            response = requests.get(url, data=to_json(data))
        if response.status_code == 401 and response.json()['error_description'] == 'Access token is not valid.':
            # Unauthorized -> authenticate & repeat request
            error_message = 'Provider 2 Unauthorized: {} {}'.format(response.json()['error_description'], auth_token)
            self._logging(error_message, url, 'error')
            info_message = 'Reconnect to Provider 2'
            self._logging(info_message)
            self.authenticate()
            response = requests.get(url, data=to_json(data))

        tools.check_request_timeout()  # Application hook to return 408 if time is over
        return response.headers, response.json()


    def authenticate(self) -> None:
        ac = api_config[self._name]
        auth_data = {
            'grant_type': 'client_credentials',
            'client_id': ac['client_id'],
            'client_secret': ac['client_secret']
        }
        headers = {'Content-Type': 'application/x-www-form-urlencoded'}
        url = '{}{}'.format(ac['url'], 'oauth/access_token')
        try:  # Authenticate
            response = requests.post(url, data=auth_data, headers=headers)
            if 'error' in response.json():
                raise AuthenticationError(response.json()['error_description'])

            self._abiospool.auth_token = response.json()['access_token']
        except Exception as e:
            error_message = 'Authenticate to Provider 2 was not succesfull: {}'.format(e)
            self._logging(error_message, url, 'error')
            raise cherrypy.HTTPError(401, error_message)


    def monitor(self) -> float:
        # Try to authenticate at REST endpoint
        start_time = time.perf_counter()
        if self.authenticate() is False:
            return False
        return time.perf_counter() - start_time
class Watcher(WatcherInterface):
    '''Define concrete Adapter
    '''
    _name = 'provider1'
    lib_pool = LibPool()

    def __init__(self, *args, **kwargs):
        self.sql = self.lib_pool.libsql
        self.send_request = cherrypy.thread_data.client_obj.send_request
        if 'game_name' in kwargs:
            self._game_name = kwargs['game_name']
        if 'log' in kwargs:
            self._log = kwargs['log']
        self.validator = get_validator_for_api(self._name, self._game_name,
                                               self._log)
        self.transformer = get_transformer_for_api(self._name, self._game_name,
                                                   self._log)
        self.time_limit = int(get_time_limit())
        super().__init__(*args, **kwargs)

    def _log_msg(self, type: str, msg: str, **kwargs) -> None:
        if self._log:
            api_config[self._name]['log'] = True
        if api_config[self._name]['log'] is False:
            return
        if not hasattr(self, 'logger'):
            self.logger = LibPool().logger
        logdata = {'connection_details': api_config[self._name]}
        for name, value in kwargs.items():
            logdata[name] = value
        if type == 'warning':
            self.logger.warning(msg, logdata)
        elif type == 'error':
            self.logger.error(msg, logdata)

    def watch_current_games(self) -> None:
        self.sql.start_trans_mode()
        cur = self.sql.cursor()
        # If there are some new just finished games mentioned in specification founded,
        # save them in database table `current_game_watch`
        valid_tournament_ids = list(
            toolz.itertoolz.unique([
                y[self._name]['tournament_id'] for x, y in api_config[
                    '{}_tournaments'.format(self._game_name)].items()
                if y[self._name] and y[self._name]['tournament_id']
            ]))
        if not valid_tournament_ids:
            msg = 'No valid tournaments were found for Provider 2'
            self._log_msg('error', msg)
            return
        for tournament_id in valid_tournament_ids:
            series = {'last_page': 1}  # Fake for loop start
            series_current_page = 0
            while series['last_page'] > series_current_page:  # Pagination
                series_current_page += 1
                # NOTE: `&tiers[]=1` is not necessary, when we filter tournament ids,
                # its open to lower levels of tournaments for testing
                url_s = 'series?games[]={}&with[]=matches&is_over=true&tournaments[]={}&page={}'.format(
                    api_config[self._game_name]['provider1_id'], tournament_id,
                    series_current_page)
                headers, series = self.send_request(api_endpoint=url_s)
                if not series or 'data' not in series:
                    msg = 'No data were found for Provider 2'
                    self._log_msg('error', msg, url=url_s)
                    return

                limit_datetime = datetime.datetime.utcnow(
                ) - datetime.timedelta(minutes=self.time_limit)
                for serie in series['data']:
                    if (serie['end'] is None or datetime.datetime.strptime(
                            serie['end'], '%Y-%m-%d %H:%M:%S') <
                            limit_datetime):
                        continue

                    url_m = 'series/{}?with[]=matches'.format(serie['id'])
                    headers, matches = self.send_request(api_endpoint=url_m)
                    if not self.validator.validate_match(
                            url_m, matches, serie, 'current_game_invalid'):
                        continue

                    for match in matches['matches']:
                        existing = cur.qfo('''
                            SELECT * FROM "current_game_watch"
                            WHERE "data_src_game_id" = %(src_game_id)s
                            AND "is_deleted" = false
                        ''',
                                           params={'src_game_id': match['id']})
                        if existing:
                            continue

                        common_data = {
                            'data_src': self._name,
                            'game_name': self._game_name,
                            'data_src_url': url_s,
                            'data_src_game_id': match['id'],
                            'data_src_game_title': matches['title'],
                            'data_src_start_datetime': matches['start'],
                            'data_src_finish_datetime': matches['end'],
                            'data_src_tournament_id': serie['tournament_id'],
                            'data_src_tournament_title': None,
                            'insert_datetime': datetime.datetime.utcnow(),
                            'is_watching': True
                        }
                        cur.insert('current_game_watch', common_data)
                        watch_game_id = cur.get_last_id('current_game_watch')

                        url_g = 'matches/{}?with[]=summary'.format(match['id'])
                        headers, data = self.send_request(api_endpoint=url_g)

                        if not self.validator.validate_game(
                                url_g, watch_game_id, data, match,
                                'current_game_invalid'):
                            cur.update('current_game_watch', {
                                'is_watching': False,
                                'is_deleted': True
                            }, {'id': watch_game_id})

        self.sql.finish_trans_mode()

    def collect_current_data(self) -> None:
        # Look for finished games (not longer than hour ago) mentioned in database table `current_game_watch`
        # and collect data for them into tables with team and player stats
        self.sql.start_trans_mode()
        cur = self.sql.cursor()
        matches = cur.qfa(
            '''
            SELECT * FROM "current_game_watch"
            WHERE "is_watching" = true
            AND "is_deleted" = false
            AND "data_src" = %s
            AND "game_name" = %s
        ''', [self._name, self._game_name])

        for match in matches:
            url_m = 'matches/{}?with[]=summary'.format(
                match['data_src_game_id'])
            headers, data = self.send_request(api_endpoint=url_m)

            # Save game stats connection object
            stats_game_id, unchanged_game_id = self._save_game_stats_connection_objects(
                match['id'])

            # Need to find last stats id
            prev_stats_game_id = self._get_previous_game_id(match['id'])

            # Save team and player game stats
            changes_count = {}
            changes_count['team'] = self._save_team_stats(
                url_m, stats_game_id, prev_stats_game_id, unchanged_game_id,
                data)
            changes_count['player'] = self._save_player_stats(
                url_m, stats_game_id, prev_stats_game_id, unchanged_game_id,
                data)
            self._solve_changes_count_aftermath(changes_count, stats_game_id,
                                                unchanged_game_id)

            # Set watching false if its over limit now
            self._check_watching_limit(match['id'], match['insert_datetime'])

        self.sql.finish_trans_mode()

    def _save_team_stats(self, url: str, stats_game_id: int,
                         prev_stats_game_id: int, unchanged_game_id: int,
                         data: dict) -> DictNone:
        # Load last stats to have a data for comparison
        last_data = self._get_last_data(prev_stats_game_id, 'team')
        prepared_data = self.transformer.prepare_teams_data(data)
        common_data = {'data_src_url': url}
        teams_data = self.transformer.get_teams_data(prepared_data, data)
        return self._save_team_stats_common(stats_game_id, unchanged_game_id,
                                            common_data, teams_data, last_data)
Пример #3
0
class Validator(ValidatorInterface):
    '''Define concrete Adapter
    '''
    _name = 'provider2'
    lib_pool = LibPool()

    def __init__(self, *args, **kwargs):
        self.sql = self.lib_pool.libsql
        if 'game_name' in kwargs:
            self._game_name = kwargs['game_name']
        if 'log' in kwargs:
            self._log = kwargs['log']

    def _log_msg(self, type: str, msg: str, **kwargs) -> None:
        if self._log:
            api_config[self._name]['log'] = True
        if api_config[self._name]['log'] is False:
            return
        if not hasattr(self, 'logger'):
            self.logger = LibPool().logger
        logdata = {'connection_details': api_config[self._name]}
        for name, value in kwargs.items():
            logdata[name] = value
        if type == 'warning':
            self.logger.warning(msg, logdata)
        elif type == 'error':
            self.logger.error(msg, logdata)

    def validate_match(self, url: str, matches_data: dict, table: str) -> bool:
        msg = None
        if 'games' not in matches_data or not matches_data['games']:
            msg = '{} - "games" missing in data or empty'.format(
                self._game_name.upper())
            # Save it as invalid and log it
            insert_data = {
                'data_src_url': url,
                'insert_datetime': datetime.datetime.utcnow(),
                'problem_msg': msg
            }
            self.sql.cursor().insert(table, insert_data)
            self._log_msg('error', msg, url=url)
            return False
        return True

    def validate_game(self, url: str, parent_game_id: int, data: dict,
                      match_data: dict, table: str) -> bool:
        teams = [x['opponent']['id'] for x in data['match']['opponents']]
        msg = None
        if not teams or len(teams) != 2:
            msg = '{} - "opponents" empty in data or invalid for match {}'.format(
                self._game_name.upper(), match_data['id'])
        elif 'players' not in data or not data['players']:
            msg = '{} - "players" missing in data or empty for match {}'.format(
                self._game_name.upper(), match_data['id'])
        if msg:
            # Save it as invalid and log it
            insert_data = {
                'data_src_url': url,
                'insert_datetime': datetime.datetime.utcnow(),
                'problem_msg': msg
            }
            if table == 'current_game_invalid':
                insert_data['watch_game_id'] = parent_game_id
            elif table == 'past_game_invalid':
                insert_data['stats_game_id'] = parent_game_id
            self.sql.cursor().insert(table, insert_data)
            self._log_msg('error', msg, url=url)
            return False
        return True
class Watcher(WatcherInterface):
    '''Define concrete Adapter
    '''
    _name = 'provider2'
    lib_pool = LibPool()

    def __init__(self, *args, **kwargs):
        self.sql = self.lib_pool.libsql
        self.send_request = cherrypy.thread_data.client_obj.send_request
        if 'game_name' in kwargs:
            self._game_name = kwargs['game_name']
        if 'log' in kwargs:
            self._log = kwargs['log']
        self.validator = get_validator_for_api(self._name, self._game_name,
                                               self._log)
        self.time_limit = int(get_time_limit())
        super().__init__(*args, **kwargs)

    def _log_msg(self, type: str, msg: str, **kwargs) -> None:
        if self._log:
            api_config[self._name]['log'] = True
        if api_config[self._name]['log'] is False:
            return
        if not hasattr(self, 'logger'):
            self.logger = LibPool().logger
        logdata = {'connection_details': api_config[self._name]}
        for name, value in kwargs.items():
            logdata[name] = value
        if type == 'warning':
            self.logger.warning(msg, logdata)
        elif type == 'error':
            self.logger.error(msg, logdata)

    def watch_current_games(self) -> None:
        self.sql.start_trans_mode()
        cur = self.sql.cursor()
        # If there are some new just finished games mentioned in specification founded,
        # save them in database table `current_game_watch`
        valid_league_ids = list(
            toolz.itertoolz.unique([
                y[self._name]['league_id'] for x, y in api_config[
                    '{}_tournaments'.format(self._game_name)].items()
                if y[self._name] and y[self._name]['league_id']
            ]))
        if not valid_league_ids:
            msg = 'No valid leagues were found for Provider 1'
            self._log_msg('error', msg)
            return
        matches_last_page = 1  # Fake for loop start
        matches_current_page = 0
        while matches_last_page > matches_current_page:  # Pagination
            matches_current_page += 1
            url_m = '{}/matches/past?filter[status]=finished&league_id={}&page[size]=100&page[number]={}'.format(
                api_config[self._game_name]['provider2_slug'],
                str(valid_league_ids), matches_current_page)
            matches_headers, matches = self.send_request(api_endpoint=url_m)
            matches_last_page = self._round_up(
                int(matches_headers['X-Total']) /
                int(matches_headers['X-Per-Page']))
            if not matches:
                return {'return_msg': 'No data were found for Provider 1.'}
                msg = 'No data were found for Provider 1'
                self._log_msg('error', msg, url=url_m)
                return

            limit_datetime = datetime.datetime.utcnow() - datetime.timedelta(
                minutes=self.time_limit)
            for match in matches:
                if match['league_id'] not in valid_league_ids:
                    continue
                elif (match['end_at'] is None or datetime.datetime.strptime(
                        match['end_at'], '%Y-%m-%dT%H:%M:%SZ') <
                      limit_datetime):
                    continue
                if not self.validator.validate_match(url_m, match,
                                                     'current_game_invalid'):
                    continue

                for game in match['games']:
                    existing = cur.qfo('''
                        SELECT * FROM "current_game_watch"
                        WHERE "data_src_game_id" = %(src_game_id)s
                        AND "is_deleted" = false
                    ''',
                                       params={'src_game_id': game['id']})
                    if existing:
                        continue

                    common_data = {
                        'data_src': self._name,
                        'game_name': self._game_name,
                        'data_src_url': url_m,
                        'data_src_game_id': game['id'],
                        'data_src_game_title': match['name'],
                        'data_src_start_datetime': match['begin_at'],
                        'data_src_finish_datetime': match['end_at'],
                        'data_src_tournament_id': match['serie_id'],
                        'data_src_tournament_title':
                        match['serie']['full_name'],
                        'insert_datetime': datetime.datetime.utcnow(),
                        'is_watching': True
                    }
                    cur.insert('current_game_watch', common_data)
                    watch_game_id = cur.get_last_id('current_game_watch')

                    url_g = '{}/games/{}'.format(
                        api_config[self._game_name]['provider2_slug'],
                        game['id'])
                    headers, data = self.send_request(api_endpoint=url_g)

                    if not self.validator.validate_game(
                            url_m, watch_game_id, data, match,
                            'current_game_invalid'):
                        cur.update('current_game_watch', {
                            'is_watching': False,
                            'is_deleted': True
                        }, {'id': watch_game_id})

        self.sql.finish_trans_mode()

    def collect_current_data(self):
        # Look for finished games (not longer than hour ago) mentioned in database table `current_game_watch`
        # and collect data for them into tables with team and player stats
        self.sql.start_trans_mode()
        cur = self.sql.cursor()
        games = cur.qfa(
            '''
            SELECT * FROM "current_game_watch"
            WHERE "is_watching" = true
            AND "is_deleted" = false
            AND "data_src" = %s
            AND "game_name" = %s
        ''', [self._name, self._game_name])

        for game in games:
            url_g = '{}/games/{}'.format(
                api_config[self._game_name]['provider2_slug'],
                game['data_src_game_id'])
            headers, data = self.send_request(api_endpoint=url_g)

            # Save game stats connection object
            stats_game_id, unchanged_game_id = self._save_game_stats_connection_objects(
                game['id'])

            # Need to find last stats id
            prev_stats_game_id = self._get_previous_game_id(game['id'])

            # Save team and player game stats
            changes_count = {}
            changes_count['team'] = self._save_team_stats(
                url_g, stats_game_id, prev_stats_game_id, unchanged_game_id,
                game['data_src_game_id'], data, data['match']['games'])
            changes_count['player'] = self._save_player_stats(
                url_g, stats_game_id, prev_stats_game_id, unchanged_game_id,
                data)
            self._solve_changes_count_aftermath(changes_count, stats_game_id,
                                                unchanged_game_id)

            # Set watching false if its over limit now
            self._check_watching_limit(game['id'], game['insert_datetime'])

        self.sql.finish_trans_mode()

    def _save_team_stats(self, url: str, stats_game_id: int,
                         prev_stats_game_id: int, unchanged_game_id: int,
                         game_id: int, data: dict,
                         games_data: dict) -> DictNone:
        # Load last stats to have a data for comparison
        last_data = self._get_last_data(prev_stats_game_id, 'team')
        teams = [x['opponent']['id'] for x in data['match']['opponents']]
        prepared_data = self.transformer.prepare_teams_data(
            teams, data, game_id, games_data)
        common_data = {'data_src_url': url}
        teams_data = self.transformer.get_teams_data(prepared_data, data,
                                                     teams)
        return self._save_team_stats_common(stats_game_id, unchanged_game_id,
                                            common_data, teams_data, last_data)

    def _round_up(self, n, decimals=0):
        multiplier = 10**decimals
        return math.ceil(n * multiplier) / multiplier
Пример #5
0
class Validator(ValidatorInterface):
    '''Define concrete Adapter
    '''
    _name = 'provider1'
    lib_pool = LibPool()

    def __init__(self, *args, **kwargs):
        self.sql = self.lib_pool.libsql
        if 'game_name' in kwargs:
            self._game_name = kwargs['game_name']
        if 'log' in kwargs:
            self._log = kwargs['log']

    def _log_msg(self, type: str, msg: str, **kwargs) -> None:
        if self._log:
            api_config[self._name]['log'] = True
        if api_config[self._name]['log'] is False:
            return
        if not hasattr(self, 'logger'):
            self.logger = LibPool().logger
        logdata = {'connection_details': api_config[self._name]}
        for name, value in kwargs.items():
            logdata[name] = value
        if type == 'warning':
            self.logger.warning(msg, logdata)
        elif type == 'error':
            self.logger.error(msg, logdata)

    def validate_match(self, url: str, matches_data: dict,
                       tournament_data: dict, table: str) -> bool:
        msg = None
        if 'matches' not in matches_data or not matches_data['matches']:
            msg = '{} - "matches" missing in data or empty'.format(
                self._game_name.upper())
            # Save it as invalid and log it
            insert_data = {
                'data_src_url': url,
                'insert_datetime': datetime.datetime.utcnow(),
                'problem_msg': msg
            }
            self.sql.cursor().insert(table, insert_data)
            self._log_msg('error', msg, url=url)
            return False
        return True

    def validate_game(self, url: str, parent_game_id: int, data: dict,
                      match_data: dict, table: str) -> bool:
        side1 = api_config[self._game_name]['provider1_sides'][0]
        side2 = api_config[self._game_name]['provider1_sides'][1]
        msg = None
        if 'rosters' not in match_data or not match_data['rosters']:
            msg = '{} - "rosters" missing in data or empty for match {}'.format(
                self._game_name.upper(), match_data['id'])
        else:

            if 'match_summary' not in data or not data['match_summary']:
                msg = '{} - "match_summary" missing in data or empty'.format(
                    self._game_name.upper())

            elif self._game_name == 'csgo':
                if side1 not in data['match_summary'] and side2 not in data[
                        'match_summary']:
                    msg = '{} - "{}" and "{}" missing in summary data'.format(
                        self._game_name.upper(), side1, side2)
                elif side1 not in data['match_summary']:
                    msg = '{} - "{}" missing in summary data'.format(
                        self._game_name.upper(), side1)
                elif side2 not in data['match_summary']:
                    msg = '{} - "{}" missing in summary data'.format(
                        self._game_name.upper(), side2)
                elif (side1 not in data['match_summary']['scoreboard']
                      and side2 not in data['match_summary']['scoreboard']):
                    msg = '{} - "{}" and "{}" missing in scoreboard data'.format(
                        self._game_name.upper(), side1, side2)
                elif side1 not in data['match_summary']['scoreboard']:
                    msg = '{} - "{}" missing in scoreboard data'.format(
                        self._game_name.upper(), side1, side2)
                elif side2 not in data['match_summary']['scoreboard']:
                    msg = '{} - "{"} missing in scoreboard data'.format(
                        self._game_name.upper(), side1, side2)

            elif self._game_name == 'dota2':
                if 'rosters' not in data or not data['rosters']:
                    msg = '{} - "rosters" missing in data or empty for match {}'.format(
                        self._game_name.upper(), match_data['id'])
                elif 'winner' not in data or data['winner'] is None:
                    msg = '{} - "winner" missing in summary data or empty'.format(
                        self._game_name.upper())
                elif 'players_stats' not in data['match_summary']:
                    msg = '{} - "players_stats" missing in data[match_summary]'.format(
                        self._game_name.upper(), side1, side2)
                else:
                    if (data['match_summary'][side1] !=
                            data['rosters'][0]['id']
                            or data['match_summary'][side2] !=
                            data['rosters'][1]['id']):
                        msg = '{} - home or away team does not correspond with rosters'.format(
                            self._game_name.upper())

            elif self._game_name == 'lol':
                if 'rosters' not in data or not data['rosters']:
                    msg = '{} - missing "rosters" in data or empty for match {}'.format(
                        self._game_name.upper(), match_data['id'])
                elif side1 not in data['match_summary'] and side2 not in data[
                        'match_summary']:
                    msg = '{} - "{}" and "{}" missing in data[match_summary]'.format(
                        self._game_name.upper(), side1, side2)
                elif side1 not in data['match_summary']:
                    msg = '{} - "{}" missing in data[match_summary]'.format(
                        self._game_name.upper(), side1)
                elif side2 not in data['match_summary']:
                    msg = '{} - "{}" missing in data[match_summary]'.format(
                        self._game_name.upper(), side2)
                elif 'players' not in data['match_summary'][
                        side1] and 'players' not in data['match_summary'][
                            side2]:
                    msg = '{} - "players" missing in data[match_summary][{}] and data[match_summary][{}]'.format(
                        self._game_name.upper(), side1, side2)
                elif 'players' not in data['match_summary'][side1]:
                    msg = '{} - "players" missing in data[match_summary][{}]'.format(
                        self._game_name.upper(), side1)
                elif 'players' not in data['match_summary'][side2]:
                    msg = '{} - "players" missing in data[match_summary][{}]'.format(
                        self._game_name.upper(), side2)
                else:
                    if (data['match_summary'][side1]['id'] !=
                            data['rosters'][0]['id']
                            or data['match_summary'][side2]['id'] !=
                            data['rosters'][1]['id']):
                        msg = '{} - home or away team does not correspond with rosters'.format(
                            self._game_name.upper())

        if msg:
            # Save it as invalid and log it
            insert_data = {
                'data_src_url': url,
                'insert_datetime': datetime.datetime.utcnow(),
                'problem_msg': msg
            }
            if table == 'current_game_invalid':
                insert_data['watch_game_id'] = parent_game_id
            elif table == 'past_game_invalid':
                insert_data['stats_game_id'] = parent_game_id
            self.sql.cursor().insert(table, insert_data)
            self._log_msg('error', msg, url=url, match_id=match_data['id'])
            return False
        return True
class RestClient(RestClientInterface):
    '''Define concrete Adapter
    '''
    _name = 'provider2'
    _pandapool = Provider2Pool()

    def __init__(self, *args, **kwargs):
        cherrypy.thread_data.client_obj = self
        if 'log' in kwargs:
            self._log = kwargs['log']
        super().__init__(*args, **kwargs)

    def _logging(self,
                 message: str,
                 url: str = None,
                 log_type: str = None) -> None:
        if self._log:
            api_config[self._name]['log'] = True
        if api_config[self._name]['log'] is False:
            return
        if not hasattr(self, 'logger'):
            self.logger = LibPool().logger
        logdata = {'connection_details': api_config[self._name]}
        if url:
            logdata['url'] = url
        if log_type is not None and log_type == 'error':
            self.logger.error(message, logdata)
        else:
            self.logger.info(message, logdata)

    def send_request(self, api_endpoint: str, data: dict = {}) -> dict:
        ac = api_config[self._name]
        url_start = '{}{}?'.format(ac['url'], api_endpoint)
        if '?' in api_endpoint:
            url_start = '{}&'.format(url_start[0:-1])
        url = '{}token={}'.format(url_start, ac['auth_token'])
        try:
            if self._pandapool.request_permission:
                response = requests.get(url, data=to_json(data))
            if response.status_code == 401:
                raise AuthenticationError(response.json()['error'])
            elif 'error' in response.json():
                raise Exception(response.json()['error'])
        except AuthenticationError as e:
            error_message = 'Authentication error on Provider 1: {}'.format(e)
            self._logging(error_message, url, 'error')
            raise cherrypy.HTTPError(401, error_message)
        except Exception as e:
            error_message = 'Request to Provider 1 was not succesfull: {}'.format(
                e)
            self._logging(error_message, url, 'error')
            raise cherrypy.HTTPError(500, error_message)

        tools.check_request_timeout(
        )  # Application hook to return 408 if time is over
        return response.headers, response.json()

    def authenticate(self) -> None:
        # Provider 1 is always authenticated, hit lives matches to check it
        url = '{}{}'.format(api_config[self._name]['url'], 'lives')
        response = requests.get(url)
        if response.status_code == 401:
            return False

    def monitor(self) -> float:
        # Try to authenticate at REST endpoint
        start_time = time.perf_counter()
        if self.authenticate() is False:
            return False
        return time.perf_counter() - start_time
class Grabber(GrabberInterface):
    '''Define concrete Adapter
    '''
    _name = 'provider1'
    lib_pool = LibPool()

    def __init__(self, *args, **kwargs):
        self.sql = self.lib_pool.libsql
        self.send_request = cherrypy.thread_data.client_obj.send_request
        if 'game_name' in kwargs:
            self._game_name = kwargs['game_name']
        if 'log' in kwargs:
            self._log = kwargs['log']
        self.validator = get_validator_for_api(self._name, self._game_name, self._log)
        self.transformer = get_transformer_for_api(self._name, self._game_name, self._log)
        super().__init__(*args, **kwargs)


    def _log_msg(self, type: str, msg: str, **kwargs) -> None:
        if self._log:
            api_config[self._name]['log'] = True
        if api_config[self._name]['log'] is False:
            return
        if not hasattr(self, 'logger'):
            self.logger = LibPool().logger
        logdata = {'connection_details': api_config[self._name]}
        for name, value in kwargs.items():
            logdata[name] = value
        if type == 'warning':
            self.logger.warning(msg, logdata)
        elif type == 'error':
            self.logger.error(msg, logdata)


    def _find_and_save_past_games(
            self, date_from: datetime.date = None, date_to: datetime.date = None, stats: dict = {}) -> dict:
        self.sql.start_trans_mode()
        cur = self.sql.cursor()
        # Find games mentioned in configuration, save them in database table `past_game_stats`
        valid_tournament_ids = list(toolz.itertoolz.unique([
            y[self._name]['tournament_id'] for x, y in api_config['{}_tournaments'.format(self._game_name)].items()
            if y[self._name] and y[self._name]['tournament_id']]))
        series = {'last_page': 1}  # Fake for loop start
        series_current_page = 0
        while series['last_page'] > series_current_page:  # Pagination
            series_current_page += 1
            # NOTE: `&tiers[]=1` is not necessary, when we filter tournament ids,
            # its open to lower levels of tournaments for testing
            # NOTE: &tournaments[]=123,345 does not work as expected, should be only one ID -> DO NOT USE IT!!!
            url_s = 'series?games[]={}&with[]=matches&page={}'.format(
                api_config[self._game_name]['provider1_id'],
                series_current_page)
            headers, series = self.send_request(api_endpoint=url_s)
            if not series or 'data' not in series:
                return {'return_msg': 'No data were found for Provider 2.'}

            limit_from = None
            if date_from:
                limit_from = datetime.datetime.strptime('{} 00:00:00'.format(date_from), '%Y-%m-%d %H:%M:%S')
            limit_to = None
            if date_to:
                limit_to = datetime.datetime.strptime('{} 23:59:59'.format(date_to), '%Y-%m-%d %H:%M:%S')
            for serie in series['data']:
                if serie['tournament_id'] not in valid_tournament_ids:
                    continue
                elif ((limit_from and limit_to
                        and serie['start'] and serie['end']
                        and (datetime.datetime.strptime(serie['start'], '%Y-%m-%d %H:%M:%S') < limit_from
                             or datetime.datetime.strptime(serie['end'], '%Y-%m-%d %H:%M:%S') > limit_to))
                        or (limit_from and serie['start']
                            and datetime.datetime.strptime(serie['start'], '%Y-%m-%d %H:%M:%S')
                            < limit_from)
                        or (limit_to and serie['end']
                            and datetime.datetime.strptime(serie['end'], '%Y-%m-%d %H:%M:%S')
                            > limit_to)):
                    continue

                stats['matches_total_count'] += 1
                url_m = 'series/{}?with[]=matches'.format(serie['id'])
                headers, matches = self.send_request(api_endpoint=url_m)
                if not self.validator.validate_match(url_m, matches, serie, 'past_game_invalid'):
                    stats['matches_invalid_count'] += 1
                    continue

                for match in matches['matches']:
                    stats['games_total_count'] += 1
                    existing = cur.qfo('''
                        SELECT * FROM "past_game_stats" WHERE "data_src_game_id" = %(src_game_id)s
                    ''', params={'src_game_id': match['id']})
                    common_data = {
                        'data_src': self._name,
                        'game_name': self._game_name,
                        'data_src_url': url_s,
                        'data_src_game_id': match['id'],
                        'data_src_game_title': serie['title'],
                        'data_src_start_datetime': matches['start'],
                        'data_src_finish_datetime': matches['end'],
                        'data_src_tournament_id': serie['tournament_id'],
                        'data_src_tournament_title': None
                    }
                    if not existing:
                        common_data['insert_datetime'] = datetime.datetime.utcnow()
                        cur.insert('past_game_stats', common_data)
                        stats_game_id = cur.get_last_id('past_game_stats')
                    else:
                        stats_game_id = existing['id']
                        del(existing['id'], existing['insert_datetime'], existing['update_datetime'])
                        diff_data = dict(toolz.itertoolz.diff(common_data, existing))
                        if diff_data:
                            common_data['update_datetime'] = datetime.datetime.utcnow()
                            cur.update('past_game_stats', diff_data, conditions={'data_src_game_id': match['id']})

                    url_g = 'matches/{}?with[]=summary'.format(match['id'])
                    headers, data = self.send_request(api_endpoint=url_g)

                    if not self.validator.validate_game(url_g, stats_game_id, data, match, 'past_game_invalid'):
                        print(url_g)
                        stats['games_invalid_count'] += 1
                        continue

                    # Save team and player game stats
                    stats['datapoints_missing_count'] += self._save_team_stats(url_m, stats_game_id, data)
                    stats['datapoints_missing_count'] += self._save_player_stats(url_m, stats_game_id, data)

        # Save statistics into "past_game_analysis" table
        final_stats = self._transform_stats_and_save_analysis(self._name, stats)

        self.sql.finish_trans_mode()
        return final_stats


    def _save_team_stats(
            self, url: str, stats_game_id: int, data: dict) -> int:
        prepared_data = self.transformer.prepare_teams_data(data)
        common_data = {
            'stats_game_id': stats_game_id,
            'data_src_url': url
        }
        teams_data = self.transformer.get_teams_data(prepared_data, data)
        return self._save_team_stats_common(common_data, teams_data)