From f5a34f3ef292861858ee305d028296433f858813 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Mon, 4 Jan 2016 22:16:43 -0500 Subject: [PATCH 01/20] Adds GameMinute (appearance) object --- tests/test_gameminute.py | 26 ++++++++++++++++++++++++++ trapp/gameminute.py | 17 +++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 tests/test_gameminute.py create mode 100644 trapp/gameminute.py diff --git a/tests/test_gameminute.py b/tests/test_gameminute.py new file mode 100644 index 0000000..07616b2 --- /dev/null +++ b/tests/test_gameminute.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +from trapp.gameminute import GameMinute + + +def test_gameminute_init(): + gm = GameMinute() + # object types + assert isinstance(gm, GameMinute) + assert isinstance(gm.data, dict) + # Default values + + +def test_gameminute_connect(): + gm = GameMinute() + assert hasattr(gm, 'db') is False + gm.connectDB() + assert hasattr(gm, 'db') + + +def test_gameminute_disconnect(): + gm = GameMinute() + gm.connectDB() + assert hasattr(gm, 'db') + gm.disconnectDB() + assert hasattr(gm, 'db') is False diff --git a/trapp/gameminute.py b/trapp/gameminute.py new file mode 100644 index 0000000..c71e95f --- /dev/null +++ b/trapp/gameminute.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +from trapp.database import Database + + +class GameMinute(): + + def __init__(self): + self.data = {} + + def connectDB(self): + self.db = Database() + self.db.connect() + + def disconnectDB(self): + self.db.disconnect() + del self.db From ccbd87d0cc8ec1c6b4e833a7f22d9937f38ddcd9 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Tue, 5 Jan 2016 00:16:53 -0500 Subject: [PATCH 02/20] Adds saveDict method to GameMinute. Also adds tbl_gameminutes to testing framework --- .travis.yml | 1 + tests/fixtures/tbl_gameminutes.sql | 37 +++++++++++++++++++++++++ tests/test_gameminute.py | 39 +++++++++++++++++++++++++++ trapp/gameminute.py | 43 ++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 tests/fixtures/tbl_gameminutes.sql diff --git a/.travis.yml b/.travis.yml index 44bd258..4d48a2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ services: - mysql before_script: - mysql -e 'create database trapp;' + - sh -c "mysql < tests/fixtures/tbl_gameminutes.sql" - sh -c "mysql < tests/fixtures/tbl_games.sql" - sh -c "mysql < tests/fixtures/tbl_players.sql" - sh -c "mysql < tests/fixtures/tbl_teams.sql" diff --git a/tests/fixtures/tbl_gameminutes.sql b/tests/fixtures/tbl_gameminutes.sql new file mode 100644 index 0000000..5e286e5 --- /dev/null +++ b/tests/fixtures/tbl_gameminutes.sql @@ -0,0 +1,37 @@ +/* +SQLyog Community v10.3 +MySQL - 5.5.28-log : Database - trapp +********************************************************************* +*/ +USE trapp; + +/*!40101 SET NAMES utf8 */; + +/*!40101 SET SQL_MODE=''*/; + +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +/*Table structure for table `tbl_gameminutes` */ + +DROP TABLE IF EXISTS `tbl_gameminutes`; + +CREATE TABLE `tbl_gameminutes` ( + `ID` double NOT NULL AUTO_INCREMENT, + `GameID` int(11) DEFAULT NULL, + `TeamID` double DEFAULT NULL, + `PlayerID` double DEFAULT NULL, + `TimeOn` tinyint(3) unsigned DEFAULT '0', + `TimeOff` tinyint(3) unsigned DEFAULT '0', + `Ejected` int(11) DEFAULT '0', + PRIMARY KEY (`ID`), + UNIQUE KEY `ID` (`ID`), + KEY `ID_2` (`ID`), + KEY `GameID` (`GameID`) +) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; diff --git a/tests/test_gameminute.py b/tests/test_gameminute.py index 07616b2..f227c2e 100644 --- a/tests/test_gameminute.py +++ b/tests/test_gameminute.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import +import pytest from trapp.gameminute import GameMinute +from trapp.log import Log def test_gameminute_init(): @@ -24,3 +26,40 @@ def test_gameminute_disconnect(): assert hasattr(gm, 'db') gm.disconnectDB() assert hasattr(gm, 'db') is False + + +def test_gameminute_saveDict(): + log = Log('test.log') + gm = GameMinute() + gm.connectDB() + + # Formats + with pytest.raises(RuntimeError) as excinfo: + data = 'foo' + gm.saveDict(data, log) + assert 'saveDict requires a dictionary' in str(excinfo.value) + + # Inserts + data = { + 'MatchID': 1, + 'TeamID': 1, + 'PlayerID': 1, + 'TimeOn': 0, + 'TimeOff': 90, + 'Ejected': 0 + } + assert gm.saveDict(data, log) is True + assert gm.db.warnings() is None + + # Updates + data = { + 'ID': 1, + 'MatchID': 2, + 'TeamID': 2, + 'PlayerID': 2, + 'TimeOn': 0, + 'TimeOff': 89, + 'Ejected': 1 + } + assert gm.saveDict(data, log) is True + assert gm.db.warnings() is None diff --git a/trapp/gameminute.py b/trapp/gameminute.py index c71e95f..15d1319 100644 --- a/trapp/gameminute.py +++ b/trapp/gameminute.py @@ -15,3 +15,46 @@ def connectDB(self): def disconnectDB(self): self.db.disconnect() del self.db + + def saveDict(self, data, log): + # Verify that data is a dictionary + if not (isinstance(data, dict)): + raise RuntimeError('saveDict requires a dictionary') + + if ('ID' in data): + # Update + log.message('Record ID provided - we update') + sql = ('UPDATE tbl_gameminutes SET ' + 'GameID = %s, ' + 'TeamID = %s, ' + 'PlayerID = %s, ' + 'TimeOn = %s, ' + 'TimeOff = %s, ' + 'Ejected = %s ' + 'WHERE ID = %s') + rs = self.db.query(sql, ( + data['MatchID'], + data['TeamID'], + data['PlayerID'], + data['TimeOn'], + data['TimeOff'], + data['Ejected'], + data['ID'] + )) + else: + log.message('No Record ID provided - we insert') + sql = ('INSERT INTO tbl_gameminutes ' + '(GameID, TeamID, PlayerID, TimeOn, TimeOff, Ejected)' + 'VALUES ' + '(%s, %s, %s, %s, %s, %s)') + rs = self.db.query(sql, ( + data['MatchID'], + data['TeamID'], + data['PlayerID'], + data['TimeOn'], + data['TimeOff'], + data['Ejected'] + )) + log.message(str(rs)) + + return True From e7ad0b2819cb4c61c2c360822e2d5a941dfbb7fa Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Tue, 5 Jan 2016 08:37:13 -0500 Subject: [PATCH 03/20] Standardize on CamelCase for dictionary key names Signed-off-by: Matt Bernhardt --- tests/test_gameminute.py | 4 ++-- tests/test_importer.py | 10 +++++----- trapp/gameminute.py | 4 ++-- trapp/importer.py | 32 ++++++++++++++++---------------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/test_gameminute.py b/tests/test_gameminute.py index f227c2e..899836d 100644 --- a/tests/test_gameminute.py +++ b/tests/test_gameminute.py @@ -41,7 +41,7 @@ def test_gameminute_saveDict(): # Inserts data = { - 'MatchID': 1, + 'GameID': 1, 'TeamID': 1, 'PlayerID': 1, 'TimeOn': 0, @@ -54,7 +54,7 @@ def test_gameminute_saveDict(): # Updates data = { 'ID': 1, - 'MatchID': 2, + 'GameID': 2, 'TeamID': 2, 'PlayerID': 2, 'TimeOn': 0, diff --git a/tests/test_importer.py b/tests/test_importer.py index 0478a7b..832a05c 100644 --- a/tests/test_importer.py +++ b/tests/test_importer.py @@ -102,7 +102,7 @@ def test_importer_parsePlayer(excel, lineup): player = 'Sample Player' result = importer.parsePlayer(player, game, team) assert len(result) == 1 - assert result == [{'playername': 'Sample Player', 'timeon': 0, 'timeoff': 90, 'ejected': False, 'matchid': 1, 'teamid': 1}] + assert result == [{'PlayerName': 'Sample Player', 'TimeOn': 0, 'TimeOff': 90, 'Ejected': False, 'GameID': 1, 'TeamID': 1}] player = "Sample Player (Substitution 50')" result = importer.parsePlayer(player, game, team) assert len(result) == 2 @@ -115,10 +115,10 @@ def test_importer_parsePlayer(excel, lineup): player = 'Sample Player (First Substitution 50 (Second Substitution 76 (Third Substitution 84 (sent off 88))))' result = importer.parsePlayer(player, game, team) assert len(result) == 4 - assert result[3]['playername'] == 'Third Substitution' - assert result[3]['ejected'] is True - assert result[3]['timeon'] == 84 - assert result[3]['timeoff'] == 88 + assert result[3]['PlayerName'] == 'Third Substitution' + assert result[3]['Ejected'] is True + assert result[3]['TimeOn'] == 84 + assert result[3]['TimeOff'] == 88 # starter = 'Sample Player' # gameID = 1 diff --git a/trapp/gameminute.py b/trapp/gameminute.py index 15d1319..553b784 100644 --- a/trapp/gameminute.py +++ b/trapp/gameminute.py @@ -33,7 +33,7 @@ def saveDict(self, data, log): 'Ejected = %s ' 'WHERE ID = %s') rs = self.db.query(sql, ( - data['MatchID'], + data['GameID'], data['TeamID'], data['PlayerID'], data['TimeOn'], @@ -48,7 +48,7 @@ def saveDict(self, data, log): 'VALUES ' '(%s, %s, %s, %s, %s, %s)') rs = self.db.query(sql, ( - data['MatchID'], + data['GameID'], data['TeamID'], data['PlayerID'], data['TimeOn'], diff --git a/trapp/importer.py b/trapp/importer.py index 698573a..7649ccb 100644 --- a/trapp/importer.py +++ b/trapp/importer.py @@ -174,14 +174,14 @@ def adjustTimeOff(self, result, lastOff): # Need to track backwards through list, transferring timeoff for x in reversed(result): - x['timeoff'] = lastOff + x['TimeOff'] = lastOff if (sentOff is True): - x['ejected'] = True + x['Ejected'] = True sentOff = False - if (x['playername'] == 'sent off' or x['playername'] == 'ejected'): + if (x['PlayerName'] == 'sent off' or x['PlayerName'] == 'ejected'): result.remove(x) sentOff = True - lastOff = x['timeon'] + lastOff = x['TimeOn'] return result @@ -307,12 +307,12 @@ def parsePlayer(self, starter, gameID, teamID): outer = self.parsePlayerRemoveTime(outer) # store outer result.append({ - 'playername': outer.strip(), - 'timeon': timeon, - 'timeoff': timeoff, - 'ejected': False, - 'matchid': gameID, - 'teamid': teamID + 'PlayerName': outer.strip(), + 'TimeOn': timeon, + 'TimeOff': timeoff, + 'Ejected': False, + 'GameID': gameID, + 'TeamID': teamID }) # parse last value @@ -320,12 +320,12 @@ def parsePlayer(self, starter, gameID, teamID): starter = self.parsePlayerRemoveTime(starter) # store last value result.append({ - 'playername': starter.strip(), - 'timeon': timeon, - 'timeoff': timeoff, - 'ejected': False, - 'matchid': gameID, - 'teamid': teamID + 'PlayerName': starter.strip(), + 'TimeOn': timeon, + 'TimeOff': timeoff, + 'Ejected': False, + 'GameID': gameID, + 'TeamID': teamID }) # Transfer timeon values to previous player's timeoff From d3930f39dfdfe144a0eab1fe143490e0766efdda Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Tue, 5 Jan 2016 09:15:51 -0500 Subject: [PATCH 04/20] Adds lookup for PlayerID to lineup importer Signed-off-by: Matt Bernhardt --- tests/test_importer.py | 2 +- trapp/importer.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/test_importer.py b/tests/test_importer.py index 832a05c..9fef37d 100644 --- a/tests/test_importer.py +++ b/tests/test_importer.py @@ -102,7 +102,7 @@ def test_importer_parsePlayer(excel, lineup): player = 'Sample Player' result = importer.parsePlayer(player, game, team) assert len(result) == 1 - assert result == [{'PlayerName': 'Sample Player', 'TimeOn': 0, 'TimeOff': 90, 'Ejected': False, 'GameID': 1, 'TeamID': 1}] + assert result == [{'PlayerID': 0, 'PlayerName': 'Sample Player', 'TimeOn': 0, 'TimeOff': 90, 'Ejected': False, 'GameID': 1, 'TeamID': 1}] player = "Sample Player (Substitution 50')" result = importer.parsePlayer(player, game, team) assert len(result) == 2 diff --git a/trapp/importer.py b/trapp/importer.py index 7649ccb..03f89e6 100644 --- a/trapp/importer.py +++ b/trapp/importer.py @@ -305,8 +305,16 @@ def parsePlayer(self, starter, gameID, teamID): # split time from outer timeon = self.parsePlayerTimeOn(outer) outer = self.parsePlayerRemoveTime(outer) + # Lookup player ID + playerID = 0 + p = Player() + needle = { + 'PlayerName': outer.strip(), + } + # playerID = p.lookupID(needle, self.log) # store outer result.append({ + 'PlayerID': playerID, 'PlayerName': outer.strip(), 'TimeOn': timeon, 'TimeOff': timeoff, @@ -318,8 +326,16 @@ def parsePlayer(self, starter, gameID, teamID): # parse last value timeon = self.parsePlayerTimeOn(starter) starter = self.parsePlayerRemoveTime(starter) + # Lookup player ID + playerID = 0 + p = Player() + needle = { + 'PlayerName': starter.strip(), + } + # playerID = p.lookupID(needle, self.log) # store last value result.append({ + 'PlayerID': playerID, 'PlayerName': starter.strip(), 'TimeOn': timeon, 'TimeOff': timeoff, From d927ea27929f4adf0eb7defd37ff8e8cd9424e09 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Tue, 5 Jan 2016 09:16:34 -0500 Subject: [PATCH 05/20] Adds documentation to parseLineup Signed-off-by: Matt Bernhardt --- trapp/importer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/trapp/importer.py b/trapp/importer.py index 03f89e6..d69d040 100644 --- a/trapp/importer.py +++ b/trapp/importer.py @@ -262,12 +262,16 @@ def parseLineup(self, lineup, game, teamID): self.log.message('ERROR: Wrong number of starters') self.errored += 1 + # self.starters has strings for every starter, combined with any + # substitutes or sendings off. These need to be split into separate + # records for every player, which is done in parsePlayer self.players = [] for starter in self.starters: batch = self.parsePlayer(starter, game, teamID) for item in batch: self.players.append(item) self.log.message(str(self.players)) + # This method returns nothing, as its work is recorded in # self.starters and self.players. From 90f32f0bab3cb85fd66346ed6b0271129101a284 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Wed, 6 Jan 2016 08:45:30 -0500 Subject: [PATCH 06/20] Fleshes out lookupIDbyName method in Player object. Future step will be to refactor this with lookupID itself. Signed-off-by: Matt Bernhardt --- tests/test_player.py | 31 +++++++++++++++++++++++++++++++ trapp/player.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/tests/test_player.py b/tests/test_player.py index 886d6ec..b103ab7 100644 --- a/tests/test_player.py +++ b/tests/test_player.py @@ -76,6 +76,37 @@ def test_player_lookupID(): # Need a test of successful lookups +def test_player_lookupIDbyName(): + # Setup + log = Log('test.log') + p = Player() + p.connectDB() + + # Format error + with pytest.raises(RuntimeError) as excinfo: + needle = 'Wil' + p.lookupIDbyName(needle, log) + assert 'lookupIDbyName requires a dictionary' in str(excinfo.value) + + # Missing fields error + with pytest.raises(RuntimeError) as excinfo: + needle = { + 'FirstName': 'Wil', + 'LastName': 'Trapp' + } + p.lookupIDbyName(needle, log) + assert 'Submitted data is missing the following fields' in str(excinfo.value) + + needle = { + 'PlayerName': 'asdf', + } + assert p.lookupIDbyName(needle, log) == [] + + needle = { + 'PlayerName': 'Delete Me', + } + assert len(p.lookupIDbyName(needle, log)) > 1 + def test_player_merge(): p = Player() assert p.merge(1, 2) is False diff --git a/trapp/player.py b/trapp/player.py index c70dc20..e79975c 100644 --- a/trapp/player.py +++ b/trapp/player.py @@ -111,6 +111,38 @@ def lookupID(self, data, log): # How many games matched this data? return players + def lookupIDbyName(self, data, log): + # This takes in a player's name and looks up player ID based on that + # alone + if not (isinstance(data, dict)): + raise RuntimeError('lookupIDbyName requires a dictionary') + + missing = [] + required = ['PlayerName', ] + for term in required: + if term not in data: + missing.append(term) + if (len(missing) > 0): + raise RuntimeError( + 'Submitted data is missing the following fields: ' + + str(missing) + ) + + # See if any game matches these three terms + sql = ('SELECT ID ' + 'FROM tbl_players ' + 'WHERE CONCAT(FirstName," ",LastName) = %s') + rs = self.db.query(sql, ( + data['PlayerName'], + )) + if (rs.with_rows): + records = rs.fetchall() + players = [] + for player in records: + players.append(player[0]) + + return players + def merge(self, fromID, intoID): # This merges one player record into another. # It includes all related tables. From c2da26e3ad6d84b6a359bb916a2a2a1f871bdb03 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Thu, 7 Jan 2016 09:43:24 -0500 Subject: [PATCH 07/20] Trims player name in lookup method - for one-named players Signed-off-by: Matt Bernhardt --- trapp/player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trapp/player.py b/trapp/player.py index e79975c..88f2f3a 100644 --- a/trapp/player.py +++ b/trapp/player.py @@ -131,7 +131,7 @@ def lookupIDbyName(self, data, log): # See if any game matches these three terms sql = ('SELECT ID ' 'FROM tbl_players ' - 'WHERE CONCAT(FirstName," ",LastName) = %s') + 'WHERE TRIM(CONCAT(FirstName," ",LastName)) = %s') rs = self.db.query(sql, ( data['PlayerName'], )) From e611f207ca0a37fbc961a913b644df912f3283f8 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Thu, 7 Jan 2016 09:44:57 -0500 Subject: [PATCH 08/20] Adds ID lookup to GameMinute model Signed-off-by: Matt Bernhardt --- trapp/gameminute.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/trapp/gameminute.py b/trapp/gameminute.py index 553b784..c804253 100644 --- a/trapp/gameminute.py +++ b/trapp/gameminute.py @@ -16,6 +16,41 @@ def disconnectDB(self): self.db.disconnect() del self.db + def lookupID(self, data, log): + # Do we have a record of player X appearing in game Y for team Z? + if not (isinstance(data, dict)): + raise RuntimeError('lookupID requires a dictionary') + + # Check data for required fields + missing = [] + required = ['GameID', 'TeamID', 'PlayerID'] + for term in required: + if term not in data: + missing.append(term) + if (len(missing) > 0): + raise RuntimeError( + 'Submitted data is missing the following fields: ' + + str(missing) + ) + + sql = ('SELECT ID ' + 'FROM tbl_gameminutes ' + 'WHERE GameID = %s ' + ' AND TeamID = %s ' + ' AND PlayerID = %s') + rs = self.db.query(sql, ( + data['GameID'], + data['TeamID'], + data['PlayerID'] + )) + if (rs.with_rows): + records = rs.fetchall() + appearances = [] + for item in records: + appearances.append(item[0]) + + return appearances + def saveDict(self, data, log): # Verify that data is a dictionary if not (isinstance(data, dict)): From ac41163b9eee046a5d89a929b36cab3459941288 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Thu, 7 Jan 2016 09:45:33 -0500 Subject: [PATCH 09/20] For relevant appearance records, changes how GameID is stored. (as integer, not as list of one integer) Signed-off-by: Matt Bernhardt --- trapp/importer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/trapp/importer.py b/trapp/importer.py index d69d040..091d603 100644 --- a/trapp/importer.py +++ b/trapp/importer.py @@ -227,6 +227,9 @@ def importRecord(self, record): self.skipped += 1 return False # If that's the case, then we need to abort processing this game + else: + # Need to convert gameID from a list of 1 number to an integer + game = game[0] # If we make it to this point, then procesing can continue From 190ef1f8fd6b5d8f1b936c409f9792b9c83da017 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Thu, 7 Jan 2016 09:46:41 -0500 Subject: [PATCH 10/20] Adds lookup of PlayerID to player parser in lineup importer Signed-off-by: Matt Bernhardt --- trapp/importer.py | 60 ++++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/trapp/importer.py b/trapp/importer.py index 091d603..a4f19af 100644 --- a/trapp/importer.py +++ b/trapp/importer.py @@ -315,20 +315,28 @@ def parsePlayer(self, starter, gameID, teamID): # Lookup player ID playerID = 0 p = Player() + p.connectDB() needle = { 'PlayerName': outer.strip(), } - # playerID = p.lookupID(needle, self.log) - # store outer - result.append({ - 'PlayerID': playerID, - 'PlayerName': outer.strip(), - 'TimeOn': timeon, - 'TimeOff': timeoff, - 'Ejected': False, - 'GameID': gameID, - 'TeamID': teamID - }) + playerID = p.lookupIDbyName(needle, self.log) + self.log.message(str(playerID)) + if (len(playerID) == 1): + playerID = playerID[0] + # store outer + result.append({ + 'PlayerID': playerID, + 'PlayerName': outer.strip(), + 'TimeOn': timeon, + 'TimeOff': timeoff, + 'Ejected': False, + 'GameID': gameID, + 'TeamID': teamID + }) + else: + self.skipped += 1 + self.log.message('_' + str(outer.strip()) + '_ returned ' + + str(len(playerID)) + ' matches') # parse last value timeon = self.parsePlayerTimeOn(starter) @@ -336,20 +344,28 @@ def parsePlayer(self, starter, gameID, teamID): # Lookup player ID playerID = 0 p = Player() + p.connectDB() needle = { 'PlayerName': starter.strip(), } - # playerID = p.lookupID(needle, self.log) - # store last value - result.append({ - 'PlayerID': playerID, - 'PlayerName': starter.strip(), - 'TimeOn': timeon, - 'TimeOff': timeoff, - 'Ejected': False, - 'GameID': gameID, - 'TeamID': teamID - }) + playerID = p.lookupIDbyName(needle, self.log) + self.log.message(str(playerID)) + if (len(playerID) == 1): + playerID = playerID[0] + # store last value + result.append({ + 'PlayerID': playerID, + 'PlayerName': starter.strip(), + 'TimeOn': timeon, + 'TimeOff': timeoff, + 'Ejected': False, + 'GameID': gameID, + 'TeamID': teamID + }) + else: + self.skipped += 1 + self.log.message('_' + str(starter.strip()) + '_ returned ' + + str(len(playerID)) + ' matches') # Transfer timeon values to previous player's timeoff result = self.adjustTimeOff(result, timeoff) From b511f1e1bc53436884600f43b41b83dce061f1ed Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Thu, 7 Jan 2016 09:47:15 -0500 Subject: [PATCH 11/20] Adds step to store appearance record as GameMinute record. This finally closes #20. Signed-off-by: Matt Bernhardt --- trapp/importer.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/trapp/importer.py b/trapp/importer.py index a4f19af..34420c2 100644 --- a/trapp/importer.py +++ b/trapp/importer.py @@ -2,6 +2,7 @@ from __future__ import absolute_import from trapp.spreadsheet import Spreadsheet from trapp.game import Game +from trapp.gameminute import GameMinute from trapp.player import Player from trapp.team import Team @@ -237,6 +238,25 @@ def importRecord(self, record): self.parseLineup(record['Lineup'], game, teamID) # At this point we have self.players - but need to store them + for player in self.players: + self.log.message(str(player)) + gm = GameMinute() + gm.connectDB() + appearanceID = gm.lookupID(player, self.log) + if (len(appearanceID) > 1): + # We have more than one record of this player/team/game. + # This is a problem. + self.errored += 1 + # TODO: need to skip now to next for iteration + elif (len(appearanceID) == 1): + # We already have a record of this player/team/game. + # We add that appearanceID, to ensure an update operation. + player['ID'] = appearanceID[0] + gm.saveDict(player, self.log) + self.imported += 1 + else: + gm.saveDict(player, self.log) + self.imported += 1 return True From 1e814a0d31c8f46b8cb6db61b153eafc32d51a06 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Fri, 8 Jan 2016 09:11:13 -0500 Subject: [PATCH 12/20] Prevents player ID lookup for "sent off" or "ejected" Signed-off-by: Matt Bernhardt --- trapp/importer.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/trapp/importer.py b/trapp/importer.py index 34420c2..e849cc3 100644 --- a/trapp/importer.py +++ b/trapp/importer.py @@ -333,13 +333,14 @@ def parsePlayer(self, starter, gameID, teamID): timeon = self.parsePlayerTimeOn(outer) outer = self.parsePlayerRemoveTime(outer) # Lookup player ID - playerID = 0 - p = Player() - p.connectDB() - needle = { - 'PlayerName': outer.strip(), - } - playerID = p.lookupIDbyName(needle, self.log) + playerID = [0] + if (outer.strip() != 'sent off' and outer.strip() != 'ejected'): + p = Player() + p.connectDB() + needle = { + 'PlayerName': outer.strip(), + } + playerID = p.lookupIDbyName(needle, self.log) self.log.message(str(playerID)) if (len(playerID) == 1): playerID = playerID[0] @@ -362,13 +363,14 @@ def parsePlayer(self, starter, gameID, teamID): timeon = self.parsePlayerTimeOn(starter) starter = self.parsePlayerRemoveTime(starter) # Lookup player ID - playerID = 0 - p = Player() - p.connectDB() - needle = { - 'PlayerName': starter.strip(), - } - playerID = p.lookupIDbyName(needle, self.log) + playerID = [0] + if (starter.strip() != 'sent off' and starter.strip() != 'ejected'): + p = Player() + p.connectDB() + needle = { + 'PlayerName': starter.strip(), + } + playerID = p.lookupIDbyName(needle, self.log) self.log.message(str(playerID)) if (len(playerID) == 1): playerID = playerID[0] From e32663961a26f50041749fd8ae2244ff1cc84260 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Fri, 8 Jan 2016 09:11:47 -0500 Subject: [PATCH 13/20] Fixes a test temporarily - need something more robust eventually Signed-off-by: Matt Bernhardt --- tests/test_importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_importer.py b/tests/test_importer.py index 9fef37d..f77cc9e 100644 --- a/tests/test_importer.py +++ b/tests/test_importer.py @@ -102,7 +102,7 @@ def test_importer_parsePlayer(excel, lineup): player = 'Sample Player' result = importer.parsePlayer(player, game, team) assert len(result) == 1 - assert result == [{'PlayerID': 0, 'PlayerName': 'Sample Player', 'TimeOn': 0, 'TimeOff': 90, 'Ejected': False, 'GameID': 1, 'TeamID': 1}] + assert result == [{'PlayerID': 335, 'PlayerName': 'Sample Player', 'TimeOn': 0, 'TimeOff': 90, 'Ejected': False, 'GameID': 1, 'TeamID': 1}] player = "Sample Player (Substitution 50')" result = importer.parsePlayer(player, game, team) assert len(result) == 2 From 266fe2d8e216ac6db47ad970dc0d68a2ff3cd390 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Fri, 8 Jan 2016 22:10:26 -0500 Subject: [PATCH 14/20] Adds test dataset, reworks one test for an accurate ID under this dataset Signed-off-by: Matt Bernhardt --- .travis.yml | 1 + tests/fixtures/test_data.sql | 43 ++++++++++++++++++++++++++++++++++++ tests/test_importer.py | 2 +- 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/test_data.sql diff --git a/.travis.yml b/.travis.yml index 4d48a2b..87df327 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,5 +15,6 @@ before_script: - sh -c "mysql < tests/fixtures/tbl_games.sql" - sh -c "mysql < tests/fixtures/tbl_players.sql" - sh -c "mysql < tests/fixtures/tbl_teams.sql" + - sh -c "mysql < tests/fixtures/test_data.sql" script: - tox -e $TOX_ENV diff --git a/tests/fixtures/test_data.sql b/tests/fixtures/test_data.sql new file mode 100644 index 0000000..4b14316 --- /dev/null +++ b/tests/fixtures/test_data.sql @@ -0,0 +1,43 @@ +/* +SQLyog Community v10.3 +MySQL - 5.5.28-log : Database - trapp +********************************************************************* +*/ + + +/*!40101 SET NAMES utf8 */; + +/*!40101 SET SQL_MODE=''*/; + +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; +USE `trapp`; + +/*Data for the table `tbl_players` */ + +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (1,'the Rabbit','Harvey','Goalkeeper',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Bedford Falls, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (2,'Rains','Claude','Defender',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Bedford Falls, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (3,'Man','Invisible','Defender',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Bedford Falls, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (4,'Phantom','','Midfielder',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Bedford Falls, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (5,'Bogeyman','','Defender',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Bedford Falls, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (6,'Gyfre','','Defender',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Bedford Falls, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (7,'Potter','Harry','Midfielder',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Surrey, England',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (8,'Storm','Sue','Midfielder',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Bedford Falls, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (9,'Griffin','','Defender',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Bedford Falls, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (10,'Vehl','Mahr','Midfielder',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Bedford Falls, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (11,'Faustus','Doctor','Midfielder',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Bedford Falls, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (12,'Mephistopheles','','Midfielder',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Bedford Falls, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (13,'Caine','Sebastian','Forward',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Bedford Falls, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (14,'Hart','Amos','Forward',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Bedford Falls, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (15,'Player','Sample','Midfielder',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Oneonta, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (16,'Substitution','','Midfielder',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Oneonta, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (17,'Substitution','First','Midfielder',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Oneonta, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (18,'Substitution','Second','Midfielder',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Oneonta, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); +insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (19,'Substitution','Third','Midfielder',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Oneonta, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; diff --git a/tests/test_importer.py b/tests/test_importer.py index f77cc9e..eeb2eda 100644 --- a/tests/test_importer.py +++ b/tests/test_importer.py @@ -102,7 +102,7 @@ def test_importer_parsePlayer(excel, lineup): player = 'Sample Player' result = importer.parsePlayer(player, game, team) assert len(result) == 1 - assert result == [{'PlayerID': 335, 'PlayerName': 'Sample Player', 'TimeOn': 0, 'TimeOff': 90, 'Ejected': False, 'GameID': 1, 'TeamID': 1}] + assert result == [{'PlayerID': 15, 'PlayerName': 'Sample Player', 'TimeOn': 0, 'TimeOff': 90, 'Ejected': False, 'GameID': 1, 'TeamID': 1}] player = "Sample Player (Substitution 50')" result = importer.parsePlayer(player, game, team) assert len(result) == 2 From c69dcedbb63b9cc35ece4792e3c26292581c5453 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Sat, 9 Jan 2016 00:01:51 -0500 Subject: [PATCH 15/20] Abstract data verification to a separate method --- trapp/gameminute.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/trapp/gameminute.py b/trapp/gameminute.py index c804253..ca03b98 100644 --- a/trapp/gameminute.py +++ b/trapp/gameminute.py @@ -16,14 +16,14 @@ def disconnectDB(self): self.db.disconnect() del self.db - def lookupID(self, data, log): - # Do we have a record of player X appearing in game Y for team Z? + def checkData(self, data, required): + # This checks a submitted data dictionary for required fields. + # 1) data must be a dictionary if not (isinstance(data, dict)): raise RuntimeError('lookupID requires a dictionary') - # Check data for required fields + # 2) data must have certain fields missing = [] - required = ['GameID', 'TeamID', 'PlayerID'] for term in required: if term not in data: missing.append(term) @@ -33,6 +33,13 @@ def lookupID(self, data, log): str(missing) ) + def lookupID(self, data, log): + # Do we have a record of player X appearing in game Y for team Z? + + # Check submitted data for format and fields + required = ['GameID', 'TeamID', 'PlayerID'] + self.checkData(data, required) + sql = ('SELECT ID ' 'FROM tbl_gameminutes ' 'WHERE GameID = %s ' From c40ac4713ba0c830ecaaa4341c35f08264cf1dd5 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Sat, 9 Jan 2016 00:38:32 -0500 Subject: [PATCH 16/20] Increases test coverage for GameMinute object Also adds a test record Signed-off-by: Matt Bernhardt --- tests/fixtures/test_data.sql | 4 ++++ tests/test_gameminute.py | 41 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/tests/fixtures/test_data.sql b/tests/fixtures/test_data.sql index 4b14316..382bd34 100644 --- a/tests/fixtures/test_data.sql +++ b/tests/fixtures/test_data.sql @@ -15,6 +15,10 @@ MySQL - 5.5.28-log : Database - trapp /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; USE `trapp`; +/*Data for the table `tbl_gameminutes` */ + +insert into `tbl_gameminutes`(`ID`,`GameID`,`TeamID`,`PlayerID`,`TimeOn`,`TimeOff`,`Ejected`) values (1,1,2,3,0,90,0); + /*Data for the table `tbl_players` */ insert into `tbl_players`(`ID`,`LastName`,`FirstName`,`Position`,`RosterNumber`,`Picture`,`Class`,`Eligible`,`College`,`Current_Club`,`ContractStatus`,`LastClub`,`YouthClub`,`Height_Feet`,`Height_Inches`,`Birthplace`,`HomeTown`,`Citizenship`,`Bio`,`Visible`,`Award_Pts`,`Intl_Pts`,`Weight`,`DOB`,`Expansion2014`) values (1,'the Rabbit','Harvey','Goalkeeper',NULL,'coming_soon.gif',0,'0','','',NULL,NULL,NULL,NULL,NULL,NULL,'Bedford Falls, NY',NULL,NULL,0,NULL,NULL,NULL,'1980-01-01',0); diff --git a/tests/test_gameminute.py b/tests/test_gameminute.py index 899836d..d159640 100644 --- a/tests/test_gameminute.py +++ b/tests/test_gameminute.py @@ -28,6 +28,47 @@ def test_gameminute_disconnect(): assert hasattr(gm, 'db') is False +def test_gameminute_checkData(): + gm = GameMinute() + required = ['GameID', 'TeamID', 'PlayerID'] + + # This should raise a format error + with pytest.raises(RuntimeError) as excinfo: + needle = 'Foo' + gm.checkData(needle, required) + assert 'lookupID requires a dictionary' in str(excinfo.value) + + # This should raise a field error + with pytest.raises(RuntimeError) as excinfo: + needle = { + 'Foo': 'Bar' + } + gm.checkData(needle, required) + assert 'Submitted data is missing the following fields' in str(excinfo.value) + + +def test_gameminute_lookupID(): + log = Log('test.log') + gm = GameMinute() + gm.connectDB() + + needle = { + 'GameID': 1, + 'TeamID': 2, + 'PlayerID': 3 + } + result = gm.lookupID(needle, log) + assert len(result) == 1 + + needle = { + 'GameID': 0, + 'TeamID': 0, + 'PlayerID': 0 + } + result = gm.lookupID(needle, log) + assert len(result) == 0 + + def test_gameminute_saveDict(): log = Log('test.log') gm = GameMinute() From 7adc540f5f2b258abf4b681251565a923c818767 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Sat, 9 Jan 2016 01:00:51 -0500 Subject: [PATCH 17/20] Splits out player save into separate method in lineup importer --- trapp/importer.py | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/trapp/importer.py b/trapp/importer.py index e849cc3..6a8889b 100644 --- a/trapp/importer.py +++ b/trapp/importer.py @@ -191,6 +191,27 @@ def correctValues(self): record['Date'] = self.source.recoverDate(record['Date']) return True + def importPlayer(self, player): + self.log.message(str(player)) + gm = GameMinute() + gm.connectDB() + appearanceID = gm.lookupID(player, self.log) + if (len(appearanceID) > 1): + # We have more than one record of this player/team/game. + # This is a problem. + self.errored += 1 + # TODO: need to skip now to next for iteration + elif (len(appearanceID) == 1): + # We already have a record of this player/team/game. + # We add that appearanceID, to ensure an update operation. + player['ID'] = appearanceID[0] + gm.saveDict(player, self.log) + self.imported += 1 + else: + gm.saveDict(player, self.log) + self.imported += 1 + return True + def importRecord(self, record): self.log.message('Importing lineup ' + str(record)) # Need to identify gameID @@ -238,25 +259,7 @@ def importRecord(self, record): self.parseLineup(record['Lineup'], game, teamID) # At this point we have self.players - but need to store them - for player in self.players: - self.log.message(str(player)) - gm = GameMinute() - gm.connectDB() - appearanceID = gm.lookupID(player, self.log) - if (len(appearanceID) > 1): - # We have more than one record of this player/team/game. - # This is a problem. - self.errored += 1 - # TODO: need to skip now to next for iteration - elif (len(appearanceID) == 1): - # We already have a record of this player/team/game. - # We add that appearanceID, to ensure an update operation. - player['ID'] = appearanceID[0] - gm.saveDict(player, self.log) - self.imported += 1 - else: - gm.saveDict(player, self.log) - self.imported += 1 + [self.importPlayer(player) for player in self.players] return True From 6d5a6e005533db5fd5f9dff4a7e6a580048f7a63 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Sat, 9 Jan 2016 22:25:24 -0500 Subject: [PATCH 18/20] Refactors parsePlayer into a split and augment operation --- tests/test_importer.py | 11 ++++++ trapp/importer.py | 89 +++++++++++++++++++----------------------- 2 files changed, 51 insertions(+), 49 deletions(-) diff --git a/tests/test_importer.py b/tests/test_importer.py index eeb2eda..c3e74cd 100644 --- a/tests/test_importer.py +++ b/tests/test_importer.py @@ -137,6 +137,17 @@ def test_importer_parsePlayerRemoveTime(excel, lineup): assert player == 'Sample Player' +def test_importer_parsePlayerSplit(excel): + log = Log('test.log') + importer = ImporterLineups(excel, log) + string = 'Player Name' + assert importer.parsePlayerSplit(string) == ['Player Name'] + string = 'Player Name (Substitute 63)' + assert importer.parsePlayerSplit(string) == ['Player Name', 'Substitute 63'] + string = 'Sample Player (First Substitution 50 (Second Substitution 76 (Third Substitution 84 (sent off 88))))' + assert importer.parsePlayerSplit(string) == ['Sample Player', 'First Substitution 50', 'Second Substitution 76', 'Third Substitution 84', 'sent off 88'] + + def test_importer_setLog(excel): log = Log('test.log') log2 = Log('test2.log') diff --git a/trapp/importer.py b/trapp/importer.py index 6a8889b..73960ce 100644 --- a/trapp/importer.py +++ b/trapp/importer.py @@ -325,32 +325,31 @@ def parsePlayer(self, starter, gameID, teamID): result = [] timeoff = 90 - while (starter.find('(') > 0): - # calculate boundaries - begin = starter.find('(') - end = starter.rfind(')') - # split into outer and starter - outer = starter[:begin - 1] - starter = starter[begin + 1:end] - # split time from outer - timeon = self.parsePlayerTimeOn(outer) - outer = self.parsePlayerRemoveTime(outer) - # Lookup player ID + # Split the player string into a list + result = self.parsePlayerSplit(starter) + + augmented = [] + # parse each member of the list + for string in result: + # Split time from player name + timeon = self.parsePlayerTimeOn(string) + player = self.parsePlayerRemoveTime(string).strip() + + # Look up playerID playerID = [0] - if (outer.strip() != 'sent off' and outer.strip() != 'ejected'): + if (player != 'sent off' and player != 'ejected'): p = Player() p.connectDB() needle = { - 'PlayerName': outer.strip(), + 'PlayerName': player, } playerID = p.lookupIDbyName(needle, self.log) - self.log.message(str(playerID)) + if (len(playerID) == 1): playerID = playerID[0] - # store outer - result.append({ + augmented.append({ 'PlayerID': playerID, - 'PlayerName': outer.strip(), + 'PlayerName': player, 'TimeOn': timeon, 'TimeOff': timeoff, 'Ejected': False, @@ -359,41 +358,33 @@ def parsePlayer(self, starter, gameID, teamID): }) else: self.skipped += 1 - self.log.message('_' + str(outer.strip()) + '_ returned ' + + self.log.message('_' + str(player) + '_ returned ' + str(len(playerID)) + ' matches') - # parse last value - timeon = self.parsePlayerTimeOn(starter) - starter = self.parsePlayerRemoveTime(starter) - # Lookup player ID - playerID = [0] - if (starter.strip() != 'sent off' and starter.strip() != 'ejected'): - p = Player() - p.connectDB() - needle = { - 'PlayerName': starter.strip(), - } - playerID = p.lookupIDbyName(needle, self.log) - self.log.message(str(playerID)) - if (len(playerID) == 1): - playerID = playerID[0] - # store last value - result.append({ - 'PlayerID': playerID, - 'PlayerName': starter.strip(), - 'TimeOn': timeon, - 'TimeOff': timeoff, - 'Ejected': False, - 'GameID': gameID, - 'TeamID': teamID - }) - else: - self.skipped += 1 - self.log.message('_' + str(starter.strip()) + '_ returned ' + - str(len(playerID)) + ' matches') - # Transfer timeon values to previous player's timeoff - result = self.adjustTimeOff(result, timeoff) + result = self.adjustTimeOff(augmented, timeoff) + + return result + + def parsePlayerSplit(self, inputString): + # This takes in a string and splits it into a list. For example: + # "Player Name" -> ["Player Name"] + # "Player Name (Substitute 63)" -> ["Player Name", "Substitute 65"] + # ... and so fort + result = [] + + while (inputString.find('(') > 0): + # Find boundaries to split + begin = inputString.find('(') + end = inputString.rfind(')') + # Perform the split + outer = inputString[:begin - 1] + inputString = inputString[begin + 1:end] + # Append results + result.append(outer) + + # Do stuff + result.append(inputString) return result From 202b0042a95cfe1110b7000f9b572197c8f5b5b6 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Sat, 9 Jan 2016 22:38:24 -0500 Subject: [PATCH 19/20] Simplifies error checking for gameID lookup in importLineup --- trapp/importer.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/trapp/importer.py b/trapp/importer.py index 73960ce..0cdb896 100644 --- a/trapp/importer.py +++ b/trapp/importer.py @@ -239,21 +239,15 @@ def importRecord(self, record): game = g.lookupID(needle, self.log) self.log.message('Found games: ' + str(game)) - if (len(game) > 1): - self.log.message('Multiple games found') - self.skipped += 1 - return False - # If that's the case, then we need to abort processing this game - elif (len(game) == 0): - self.log.message('No matching games found') + + if (len(game) != 1): + self.log.message('Found wrong number of games: ' + str(len(game))) self.skipped += 1 + # If we didn't find one gameID, then we abort processing this game return False - # If that's the case, then we need to abort processing this game - else: - # Need to convert gameID from a list of 1 number to an integer - game = game[0] - # If we make it to this point, then procesing can continue + # Need to convert gameID from a list of 1 number to an integer + game = game[0] # Parse lineup string self.parseLineup(record['Lineup'], game, teamID) From 132b3bcefd014fe8d8470c693913223aef8fd537 Mon Sep 17 00:00:00 2001 From: Matt Bernhardt Date: Sat, 9 Jan 2016 22:47:33 -0500 Subject: [PATCH 20/20] Removes TODO for refactoring importPlayer --- trapp/importer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/trapp/importer.py b/trapp/importer.py index 0cdb896..b6d8f93 100644 --- a/trapp/importer.py +++ b/trapp/importer.py @@ -200,7 +200,6 @@ def importPlayer(self, player): # We have more than one record of this player/team/game. # This is a problem. self.errored += 1 - # TODO: need to skip now to next for iteration elif (len(appearanceID) == 1): # We already have a record of this player/team/game. # We add that appearanceID, to ensure an update operation.