Make style code conformant with black

This commit is contained in:
Christian Wolf 2023-11-19 17:07:20 +01:00
parent ca9164552c
commit 83a0003024
21 changed files with 1337 additions and 891 deletions

View File

@ -2,15 +2,17 @@ import solo_turnier
import logging
import coloredlogs
def __initLogging():
logging.basicConfig()
logging.root.setLevel(logging.NOTSET)
logger = logging.getLogger('solo_turnier')
logger = logging.getLogger("solo_turnier")
coloredlogs.install(level=5, logger=logger)
return logger
def main():
l = __initLogging()
cli = solo_turnier.cli.Cli(l)
@ -18,14 +20,14 @@ def main():
batchWorker = solo_turnier.batch.BatchWorker(cli)
if cli.showGUI():
raise Exception('Not yet implemented')
raise Exception("Not yet implemented")
elif cli.startFlaskServer():
solo_turnier.flask.startFlask(
batchWorker,
batchWorker,
debug=cli.getLogLevel() > 0,
port=cli.getPort(),
showOnlyFinalists=not cli.showAllParticipants(),
externalDebugger=cli.externalDebugger
externalDebugger=cli.externalDebugger,
)
else:
combinedData = batchWorker.run(
@ -35,5 +37,6 @@ def main():
consoleOutputtter = solo_turnier.output.ConsoleOutputter()
consoleOutputtter.output(combinedData)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@ -1,4 +1,3 @@
from . import competition_class
from . import group
from . import types

View File

@ -1,4 +1,3 @@
import solo_turnier
import logging
import os
@ -6,24 +5,31 @@ import pprint
import tabulate
class BatchWorker:
def __init__(
self,
config: solo_turnier.cli.Cli
):
self.l = logging.getLogger('solo_turnier.batch')
def __init__(self, config: solo_turnier.cli.Cli):
self.l = logging.getLogger("solo_turnier.batch")
self.config = config
def run(self, removeFilteredParicipants=True):
self.l.debug(self.config.__dict__)
locator = solo_turnier.html_locator.HtmlLocator()
self.l.info('Checking for feasible preview HTML export files in "%s"', self.config.importHtmlPath())
htmlCandidatesPreview = locator.findPreviewRoundCandidates(self.config.importHtmlPath())
self.l.debug('Found HTML file candidates for preview rounds: %s', htmlCandidatesPreview)
self.l.info(
'Checking for feasible preview HTML export files in "%s"',
self.config.importHtmlPath(),
)
htmlCandidatesPreview = locator.findPreviewRoundCandidates(
self.config.importHtmlPath()
)
self.l.debug(
"Found HTML file candidates for preview rounds: %s", htmlCandidatesPreview
)
htmlResultFiles = locator.findCandidates(self.config.importHtmlPath())
self.l.debug('Using HTML result files for result extraction: %s', htmlResultFiles)
self.l.debug(
"Using HTML result files for result extraction: %s", htmlResultFiles
)
worker = solo_turnier.worker.Worker()
importedData = worker.collectAllData(htmlCandidatesPreview, htmlResultFiles)

View File

@ -3,27 +3,58 @@ import logging
import debugpy
class Cli:
def __init__(self, l: logging.Logger):
parser = argparse.ArgumentParser()
# parser.add_argument('--gui', help='Show the GUI', action='store_true')
parser.add_argument('--no-flask', action='store_false', dest='flask', help='Disable the internal flask web server')
parser.add_argument('--port', help='The port to listen for incoming requests', default='8082')
parser.add_argument(
"--no-flask",
action="store_false",
dest="flask",
help="Disable the internal flask web server",
)
parser.add_argument(
"--port", help="The port to listen for incoming requests", default="8082"
)
parser.add_argument('html', help='The path from where to look for HTML export files', nargs=1, default=['.'])
parser.add_argument('-o', '--output', help='Set the output path of the script', nargs=1, default=[None])
parser.add_argument('--all-participants', '-a', action='store_true', help='Show all participants not only finalists')
parser.add_argument(
"html",
help="The path from where to look for HTML export files",
nargs=1,
default=["."],
)
parser.add_argument(
"-o",
"--output",
help="Set the output path of the script",
nargs=1,
default=[None],
)
parser.add_argument(
"--all-participants",
"-a",
action="store_true",
help="Show all participants not only finalists",
)
parser.add_argument('-v', '--verbose', help='Increase verbosity', action='count', default=0)
parser.add_argument('-d', '--debug', action='store_true', help='Activate debugging during startup')
parser.add_argument(
"-v", "--verbose", help="Increase verbosity", action="count", default=0
)
parser.add_argument(
"-d",
"--debug",
action="store_true",
help="Activate debugging during startup",
)
self.__args = parser.parse_args()
if self.__args.debug:
debugpy.listen(5678)
debugpy.wait_for_client()
self.externalDebugger = self.__args.debug
map = {
0: logging.ERROR,
1: logging.WARN,
@ -33,28 +64,28 @@ class Cli:
}
logLevel = map.get(self.__args.verbose, logging.DEBUG)
l.setLevel(logLevel)
def showGUI(self):
# return self.__args.gui
return False
def startFlaskServer(self):
return self.__args.flask
def importHtmlPath(self):
return self.__args.html[0]
def importCSVPath(self):
return self.__args.import_from[0]
def output(self):
return self.__args.output[0]
def getLogLevel(self):
return self.__args.verbose
def showAllParticipants(self):
return self.__args.all_participants
def getPort(self):
return int(self.__args.port)

View File

@ -1,74 +1,80 @@
import re
class CompetitionClass:
def __init__(self, text: str):
self.name = text
def __repr__(self):
return self.name
class CombinedCompetitionClass:
def __init__(self, clsA: CompetitionClass, clsB: CompetitionClass, clsC: CompetitionClass = None):
def __init__(
self,
clsA: CompetitionClass,
clsB: CompetitionClass,
clsC: CompetitionClass = None,
):
self.clsA = clsA
self.clsB = clsB
self.clsC = clsC
def __repr__(self):
if self.clsC is None:
return f'{self.clsA}/{self.clsB}'
return f"{self.clsA}/{self.clsB}"
else:
return f'{self.clsA}/{self.clsB}/{self.clsC}'
return f"{self.clsA}/{self.clsB}/{self.clsC}"
Class_t = CompetitionClass | CombinedCompetitionClass
class CompetitionClassParser:
NEWC = CompetitionClass('Newc.')
BEG = CompetitionClass('Beg.')
ADV = CompetitionClass('Adv.')
NEWC = CompetitionClass("Newc.")
BEG = CompetitionClass("Beg.")
ADV = CompetitionClass("Adv.")
PREVIEW = CompetitionClass("Sichtung")
PREVIEW = CompetitionClass('Sichtung')
def __init__(self):
self.mapNames = {
'Newc': self.NEWC,
'Newc.': self.NEWC,
'Newcomer': self.NEWC,
'Beg': self.BEG,
'Beg.': self.BEG,
'Beginner': self.BEG,
'Adv': self.ADV,
'Adv.': self.ADV,
'Advanced': self.ADV,
"Newc": self.NEWC,
"Newc.": self.NEWC,
"Newcomer": self.NEWC,
"Beg": self.BEG,
"Beg.": self.BEG,
"Beginner": self.BEG,
"Adv": self.ADV,
"Adv.": self.ADV,
"Advanced": self.ADV,
}
self.namesPreview = [
'Sichtung'
]
self.namesPreview = ["Sichtung"]
self.mapShortNames = {
'N': self.NEWC,
'B': self.BEG,
'A': self.ADV,
"N": self.NEWC,
"B": self.BEG,
"A": self.ADV,
}
def parseClass(self, cls: str, allowPreview: bool = False) -> Class_t:
if allowPreview and cls in self.namesPreview:
return self.PREVIEW
match = re.compile('^(\\w+\\.?)/(\\w+\\.?)$').match(cls)
return self.PREVIEW
match = re.compile("^(\\w+\\.?)/(\\w+\\.?)$").match(cls)
if match is not None:
clsA = self.mapNames[match.group(1)]
clsB = self.mapNames[match.group(2)]
return CombinedCompetitionClass(clsA, clsB)
else:
return self.mapNames[cls]
def parseAbbreviatedClass(self, cls: str) -> Class_t:
return self.mapShortNames[cls]
def isPureClass(self, cls: str, allowPreview: bool = False) -> bool:
parsedClass = self.parseClass(cls, allowPreview)
return isinstance(parsedClass, CompetitionClass)
def getAllClasses(self) -> list[CompetitionClass]:
return [self.NEWC, self.BEG, self.ADV]

View File

@ -4,29 +4,29 @@ import logging
_l = logging.getLogger(__name__)
def startFlask(
batchWorker: solo_turnier.batch.BatchWorker,
debug: bool = False,
port: int = 8082,
showOnlyFinalists: bool = True,
externalDebugger: bool = False,
):
):
app = flask.Flask(__name__)
@app.route('/')
@app.route("/")
def index():
combinedData = batchWorker.run(False)
_l.debug('Show only finalists %s', showOnlyFinalists)
_l.debug("Show only finalists %s", showOnlyFinalists)
return flask.render_template('index.html', data=combinedData, onlyFinalists=showOnlyFinalists)
@app.get('/custom.css')
def css():
ret = flask.render_template(
'custom.css',
onlyFinalists=showOnlyFinalists
return flask.render_template(
"index.html", data=combinedData, onlyFinalists=showOnlyFinalists
)
return flask.Response(ret, mimetype='text/css')
@app.get("/custom.css")
def css():
ret = flask.render_template("custom.css", onlyFinalists=showOnlyFinalists)
return flask.Response(ret, mimetype="text/css")
useReloader = debug and not externalDebugger
app.run(host='0.0.0.0', port=port, debug=debug, use_reloader=useReloader)
app.run(host="0.0.0.0", port=port, debug=debug, use_reloader=useReloader)

View File

@ -1,102 +1,101 @@
import re
class Group:
def __init__(self, text: str):
self.name = text
def __repr__(self):
return self.name
def getContainedGroups(self):
return (self,)
class CombinedGroup:
def __init__(self, grpA: Group, grpB: Group):
self.clsA = grpA
self.clsB = grpB
def __repr__(self):
return f'{self.clsA}/{self.clsB}'
return f"{self.clsA}/{self.clsB}"
def getContainedGroups(self):
return (self.clsA, self.clsB)
Group_t = Group | CombinedGroup
class GroupParser:
KIN = Group('Kin.')
JUN = Group('Jun.')
JUG = Group('Jug.')
HGR = Group('Hgr.')
MAS1 = Group('Mas. I')
MAS2 = Group('Mas. II')
MAS3 = Group('Mas. III')
MAS4 = Group('Mas. IV')
MAS5 = Group('Mas. V')
KIN = Group("Kin.")
JUN = Group("Jun.")
JUG = Group("Jug.")
HGR = Group("Hgr.")
MAS1 = Group("Mas. I")
MAS2 = Group("Mas. II")
MAS3 = Group("Mas. III")
MAS4 = Group("Mas. IV")
MAS5 = Group("Mas. V")
def __init__(self):
self.mapNames = {
'Kin': self.KIN,
'Kin.': self.KIN,
'Kinder': self.KIN,
'Jun': self.JUN,
'Jun.': self.JUN,
'Junioren': self.JUN,
'Jug': self.JUG,
'Jug.': self.JUG,
'Jugend': self.JUG,
'Hgr': self.HGR,
'HGr': self.HGR,
'Hgr.': self.HGR,
'HGr.': self.HGR,
'Hauptgruppe': self.HGR,
'Mas. I': self.MAS1,
'Mas. II': self.MAS2,
'Mas. III': self.MAS3,
'Mas. IV': self.MAS4,
'Mas. V': self.MAS5,
'Mas I': self.MAS1,
'Mas II': self.MAS2,
'Mas III': self.MAS3,
'Mas IV': self.MAS4,
'Mas V': self.MAS5,
'Masters I': self.MAS1,
'Masters II': self.MAS2,
'Masters III': self.MAS3,
'Masters IV': self.MAS4,
'Masters V': self.MAS5,
"Kin": self.KIN,
"Kin.": self.KIN,
"Kinder": self.KIN,
"Jun": self.JUN,
"Jun.": self.JUN,
"Junioren": self.JUN,
"Jug": self.JUG,
"Jug.": self.JUG,
"Jugend": self.JUG,
"Hgr": self.HGR,
"HGr": self.HGR,
"Hgr.": self.HGR,
"HGr.": self.HGR,
"Hauptgruppe": self.HGR,
"Mas. I": self.MAS1,
"Mas. II": self.MAS2,
"Mas. III": self.MAS3,
"Mas. IV": self.MAS4,
"Mas. V": self.MAS5,
"Mas I": self.MAS1,
"Mas II": self.MAS2,
"Mas III": self.MAS3,
"Mas IV": self.MAS4,
"Mas V": self.MAS5,
"Masters I": self.MAS1,
"Masters II": self.MAS2,
"Masters III": self.MAS3,
"Masters IV": self.MAS4,
"Masters V": self.MAS5,
}
def parseClass(self, cls: str) -> Group_t:
match = re.compile('^(\\w+\\.?)/(\\w+\\.?)$').match(cls)
match = re.compile("^(\\w+\\.?)/(\\w+\\.?)$").match(cls)
if match is not None:
grpA = self.mapNames[match.group(1)]
grpB = self.mapNames[match.group(2)]
return CombinedGroup(grpA, grpB)
else:
return self.mapNames[cls]
def isPureClass(self, cls: str) -> bool:
parsedClass = self.parseClass(cls)
return isinstance(parsedClass, Group)
def getGroups(self) -> list[Group]:
return[
GroupParser.KIN,
GroupParser.JUN,
GroupParser.JUG,
GroupParser.HGR,
GroupParser.MAS1,
GroupParser.MAS2,
GroupParser.MAS3,
GroupParser.MAS4,
GroupParser.MAS5
return [
GroupParser.KIN,
GroupParser.JUN,
GroupParser.JUG,
GroupParser.HGR,
GroupParser.MAS1,
GroupParser.MAS2,
GroupParser.MAS3,
GroupParser.MAS4,
GroupParser.MAS5,
]
def getGroupsAsSortedList(self, groups) -> list[Group]:
return [x for x in self.getGroups() if x in groups]

View File

@ -1,36 +1,37 @@
import os
import logging
class HtmlLocator:
def __init__(self):
self.l = logging.getLogger('solo_turnier.html_locator')
self.l = logging.getLogger("solo_turnier.html_locator")
def __findRecursivelyCandidates(self, path: str, fileName: str):
ret = []
ls = os.listdir(path)
if fileName in ls and os.path.isfile(os.path.join(path, fileName)):
ret.append(os.path.join(path, fileName))
for p in ls:
subPath = os.path.join(path, p)
if os.path.isdir(subPath):
ret = ret + self.__findRecursivelyCandidates(subPath, fileName)
return ret
def __fingMatchingTabs(self, ergCandidate):
path = os.path.dirname(ergCandidate)
tabPath = os.path.join(path, 'tabges.htm')
tabPath = os.path.join(path, "tabges.htm")
if not os.path.exists(tabPath):
tabPath = None
return (ergCandidate, tabPath)
def findCandidates(self, path: str):
candidatesErg = self.__findRecursivelyCandidates(path, 'erg.htm')
candidatesErg = self.__findRecursivelyCandidates(path, "erg.htm")
candidates = [self.__fingMatchingTabs(x) for x in candidatesErg]
return candidates
def findPreviewRoundCandidates(self, path: str):
candidates = self.__findRecursivelyCandidates(path, 'tabges.htm')
candidates = self.__findRecursivelyCandidates(path, "tabges.htm")
return candidates

View File

@ -8,44 +8,45 @@ from .types import HtmlPreviewImport as HtmlImport, HtmlResultImport
from .group import GroupParser
from .competition_class import CompetitionClassParser
class IncompleteRoundException(Exception):
def __init__(self, *args):
super(IncompleteRoundException, self).__init__(*args)
class HtmlParser:
class HtmlParser:
def __init__(self, text: str, fileName: str = None):
self.l = logging.getLogger('solo_turnier.html_parser')
self.soup = BeautifulSoup(text, 'html.parser')
self.l = logging.getLogger("solo_turnier.html_parser")
self.soup = BeautifulSoup(text, "html.parser")
self.fileName = fileName
self.groupParser = GroupParser()
self.classParser = CompetitionClassParser()
def __repr__(self):
if self.fileName is None:
return 'HtmlParser(direct text)'
return "HtmlParser(direct text)"
else:
return f'HtmlParser({self.fileName})'
def getEventTitle(self):
return self.soup.find('div', class_='eventhead').table.tr.td.contents[0]
return f"HtmlParser({self.fileName})"
def guessDataFromHtmlTitle(self, title = None):
def getEventTitle(self):
return self.soup.find("div", class_="eventhead").table.tr.td.contents[0]
def guessDataFromHtmlTitle(self, title=None):
if title is None:
title = self.getEventTitle()
match = re.compile('.*?ETW, Solos (.*?)(?: ".*")?').fullmatch(title)
if match is None:
self.l.error('Cannot parse html title "%s". Possible bug?', title)
raise Exception(f'Cannot parse title "{title}"')
rest = match.group(1)
rawGroup, rawClass, dance = rest.split(' ', 2)
rawGroup, rawClass, dance = rest.split(" ", 2)
return {
'dance': dance.strip(),
'class_': str(self.classParser.parseClass(rawClass, True)),
'group': str(self.groupParser.parseClass(rawGroup))
"dance": dance.strip(),
"class_": str(self.classParser.parseClass(rawClass, True)),
"group": str(self.groupParser.parseClass(rawGroup)),
}
def parseResult(self):
@ -53,47 +54,47 @@ class HtmlParser:
def __parseRows(rows, finalist: bool):
def __parseRow(row):
tds = row.find_all('td')
tds = row.find_all("td")
if len(tds) != 2:
return
if tds[1].contents[0].startswith('Alle Starter weiter genommen.'):
self.l.info('No excluded starters found.')
if tds[1].contents[0].startswith("Alle Starter weiter genommen."):
self.l.info("No excluded starters found.")
return
regex = re.compile('(.*) \\(([0-9]+)\\)')
regex = re.compile("(.*) \\(([0-9]+)\\)")
place = tds[0].contents[0]
match = regex.fullmatch(tds[1].contents[0])
if match is None:
self.l.error('Could not match %s to regex search pattern', str(tds))
raise Exception(f'Could not match {tds} to regex search pattern')
self.l.error("Could not match %s to regex search pattern", str(tds))
raise Exception(f"Could not match {tds} to regex search pattern")
name = match.group(1)
number = match.group(2)
participant = HtmlParticipant(name, number)
participant.finalist = finalist
participants[participant] = place
for row in rows:
__parseRow(row)
def __parseFirstTable(table):
roundName = table.tr.td.contents[0]
if roundName != 'Endrunde':
self.l.warning('Found table with round name %s.', roundName)
raise IncompleteRoundException('Could not parse HTML file')
__parseRows(table.find_all('tr')[2:], True)
if roundName != "Endrunde":
self.l.warning("Found table with round name %s.", roundName)
raise IncompleteRoundException("Could not parse HTML file")
__parseRows(table.find_all("tr")[2:], True)
def __parseRemainingTables(tables):
for table in tables:
__parseRows(table.find_all('tr'), False)
__parseRows(table.find_all("tr"), False)
tables = self.soup.find("div", class_="extract").find_all("table")
tables = self.soup.find('div', class_='extract').find_all('table')
try:
if len(tables) > 0:
__parseFirstTable(tables[0])
@ -105,61 +106,58 @@ class HtmlParser:
# title = self.soup.find('div', class_='eventhead').table.tr.td.contents[0]
# ret = HtmlImport(title, participants)
ret = HtmlResultImport(participants)
ret = HtmlResultImport(participants)
return ret
def parsePreparationRound(self):
title = self.soup.find('div', class_='eventhead').table.tr.td.contents[0]
title = self.soup.find("div", class_="eventhead").table.tr.td.contents[0]
tableData = []
rowTitles = []
def __mapBr(td):
for br in td.find_all('br'):
br.replace_with('\n')
for br in td.find_all("br"):
br.replace_with("\n")
td.smooth()
return td
def __extractTitles(table):
for row in table.find_all('tr')[1:]:
for row in table.find_all("tr")[1:]:
rowTitles.append(__mapBr(row.td).string)
def __extractColumns(table):
content = []
def __extractContent(td):
for br in td.find_all('br'):
br.replace_with('\n')
for br in td.find_all("br"):
br.replace_with("\n")
span = td.span
if span is not None:
span = span.extract()
meta = span.string
else:
meta = None
td.smooth()
return {
'text': td.string.replace('\xa0', ' ').strip(),
'meta': meta
}
return {"text": td.string.replace("\xa0", " ").strip(), "meta": meta}
def __extractRow(row):
entries = []
for entry in row.find_all('td')[1:]:
for entry in row.find_all("td")[1:]:
entries.append(__extractContent(entry))
return entries
for row in table.find_all('tr')[1:]:
for row in table.find_all("tr")[1:]:
content.append(__extractRow(row))
return content
def __mergeColumns(columns1, columns2):
return list(map(lambda x, y: x + y, columns1, columns2))
extract = self.soup.find('div', class_='extract')
tables = extract.find_all('table', class_='tab1')
extract = self.soup.find("div", class_="extract")
tables = extract.find_all("table", class_="tab1")
__extractTitles(tables[0])
tableData = __extractColumns(tables[0])
@ -167,94 +165,96 @@ class HtmlParser:
for table in tables[1:]:
tableData = __mergeColumns(tableData, __extractColumns(table))
data = {
'titles': rowTitles,
'table': tableData
}
data = {"titles": rowTitles, "table": tableData}
return {"title": title, "data": data}
return {'title': title, 'data': data}
def cleanPreparationRoundImport(self, data):
def __cleanTable(table):
def __cleanText(s: str):
# print("cleaning string ", s)
return s.strip(' \n\xa0')
return s.strip(" \n\xa0")
def __cleanEntry(entry):
entry['text'] = __cleanText(entry['text'])
if entry['meta'] is not None:
entry['meta'] = __cleanText(entry['meta'])
entry["text"] = __cleanText(entry["text"])
if entry["meta"] is not None:
entry["meta"] = __cleanText(entry["meta"])
for row in table:
for entry in row:
# print(entry)
__cleanEntry(entry)
data['title'] = data['title'].strip()
__cleanTable(data['data']['table'])
data["title"] = data["title"].strip()
__cleanTable(data["data"]["table"])
def parseIndividualResult(self, competitionGroup, competitionClass, dance):
participants = {}
def __parseTable(table):
rows = table.find_all('tr')
rows = table.find_all("tr")
def __getIds():
row = rows[1]
entries = row('td')
entries = row("td")
entries = entries[1:]
entries = [x for x in entries if len(x.contents[0].strip()) > 0]
return [x.contents[0].strip() for x in entries]
ids = __getIds()
numIds = len(ids)
self.l.log(5, 'Found ids in dataset: %s', ids)
self.l.log(5, "Found ids in dataset: %s", ids)
def findRowIndex(prefixStr):
def isRowMatchingCriteria(row):
if row.td.contents[0].startswith(prefixStr):
return True
return False
l = list(map(isRowMatchingCriteria, rows))
if True not in l:
return None
return l.index(True)
def getPlaces():
placeRowIdx = findRowIndex('Platz von')
placeTags = rows[placeRowIdx]('td')[1:(numIds+1)]
placeRowIdx = findRowIndex("Platz von")
placeTags = rows[placeRowIdx]("td")[1 : (numIds + 1)]
def getSinglePlaceStr(tag):
for br in tag('br'):
br.replace_with('-')
for br in tag("br"):
br.replace_with("-")
tag.smooth()
rawStr = tag.contents[0].strip()
if rawStr.endswith('-'):
if rawStr.endswith("-"):
rawStr = rawStr[:-1]
return rawStr
places = list(map(getSinglePlaceStr, placeTags))
return places
places = getPlaces()
self.l.log(5, 'Found places: %s', places)
self.l.log(5, "Found places: %s", places)
def getClass():
classRow = findRowIndex('Startklasse')
classRow = findRowIndex("Startklasse")
if classRow is not None:
classTags = rows[classRow]('td')[1:(numIds+1)]
classTags = rows[classRow]("td")[1 : (numIds + 1)]
return list(map(lambda x: x.contents[0], classTags))
return None
classes = getClass()
self.l.log(5, 'Classes: %s', classes)
self.l.log(5, "Classes: %s", classes)
def getGroups():
groupRow = findRowIndex('Startgruppe')
groupRow = findRowIndex("Startgruppe")
if groupRow is not None:
classTags = rows[groupRow]('td')[1:(numIds+1)]
classTags = rows[groupRow]("td")[1 : (numIds + 1)]
return list(map(lambda x: x.contents[0], classTags))
return None
groups = getGroups()
self.l.log(5, 'Groups: %s', groups)
self.l.log(5, "Groups: %s", groups)
for idx, id in enumerate(ids):
cls = classes[idx] if classes is not None else None
@ -263,8 +263,8 @@ class HtmlParser:
tup = (competitionGroup, competitionClass, dance, id)
participants[tup] = (places[idx], cls, grp)
tables = self.soup.find('div', class_='extract').find_all('table')
tables = self.soup.find("div", class_="extract").find_all("table")
for table in tables:
__parseTable(table)
return HtmlResultTotalTable( participants)
return HtmlResultTotalTable(participants)

View File

@ -1,4 +1,3 @@
import logging
from tabulate import tabulate
import pprint
@ -6,14 +5,15 @@ import pprint
import solo_turnier
from solo_turnier import types
sections = ('Kin.', 'Jun.', 'Jug.', 'Sonst')
sections = ("Kin.", "Jun.", "Jug.", "Sonst")
sectionMap = {
'Kin.': 'Kinder',
'Jun.': 'Junioren',
'Jug.': 'Jugend',
'Sonst': 'Undefiniert'
"Kin.": "Kinder",
"Jun.": "Junioren",
"Jug.": "Jugend",
"Sonst": "Undefiniert",
}
class AbstractOutputter:
def __init__(self):
self.worker = solo_turnier.worker.DataWorker()
@ -24,24 +24,26 @@ class AbstractOutputter:
def getRowData(self, person: solo_turnier.worker.ResultPerson, results):
mappedResults = self.worker.mapPersonResultsToDanceList(results, self.dances)
if self.showIds:
name = f'{person.name} ({person.id})'
name = f"{person.name} ({person.id})"
else:
name = person.name
ret = [name]
for result in mappedResults:
if result is None:
ret.append('')
ret.append("")
elif result.finalist == False:
ret.append('x')
ret.append("x")
elif result.place == result.placeTo:
ret.append(f'{result.place}. ({result.class_})')
ret.append(f"{result.place}. ({result.class_})")
else:
ret.append(f'{result.place}.-{result.placeTo}. ({result.class_})')
ret.append(f"{result.place}.-{result.placeTo}. ({result.class_})")
return ret
def getTabularData(self, data, section):
sortedPersons, self.showIds = self.worker.sortPersonsInGroup(self.groups[section])
sortedPersons, self.showIds = self.worker.sortPersonsInGroup(
self.groups[section]
)
tableData = []
for person in sortedPersons:
@ -49,63 +51,66 @@ class AbstractOutputter:
return tableData
class ConsoleOutputter(AbstractOutputter):
def __init__(self):
super().__init__()
self.l = logging.getLogger('solo_turnier.output.console')
self.l = logging.getLogger("solo_turnier.output.console")
def __outputSection(self, data, section):
tableData = self.getTabularData(data, section)
tableData = [['Name'] + self.dances] + tableData
tableData = [["Name"] + self.dances] + tableData
print(f"Einzeltanzwettbewerb der {sectionMap[section]}")
print(tabulate(tableData, headers='firstrow', tablefmt='fancy_grid'))
print(tabulate(tableData, headers="firstrow", tablefmt="fancy_grid"))
print()
def _outputGroup(self, group: solo_turnier.group.Group, groupResults: types.TotalGroupResult):
def _outputGroup(
self, group: solo_turnier.group.Group, groupResults: types.TotalGroupResult
):
print(f"Einzeltanzwettbewerb der Gruppe {group}")
tableData = [['Tanz'] + groupResults.dances]
tableData = [["Tanz"] + groupResults.dances]
participants = list(groupResults.results.keys())
participants.sort(key=lambda x: (x.id, x.name))
for participant in participants:
results = groupResults.results[participant]
def mapResultColumn(result: types.SingleParticipantResult):
def getPlace(place, placeTo):
if placeTo is None:
return f'{place}.'
return f"{place}."
else:
return f'{place}.-{placeTo}.'
return f"{place}.-{placeTo}."
if result is None:
return ''
return ""
placeNative = getPlace(result.placeNative, result.placeNativeTo)
place = getPlace(result.place, result.placeTo)
lineOne = f'{placeNative} ({result.nativeClass})'
lineTwo = f'[{place} in {result.competitionClass}]'
lineOne = f"{placeNative} ({result.nativeClass})"
lineTwo = f"[{place} in {result.competitionClass}]"
lines = [lineOne, lineTwo]
if not result.finalist:
lines = ['kein/e Finalist/in'] + lines
return '\n'.join(lines)
lines = ["kein/e Finalist/in"] + lines
return "\n".join(lines)
mappedResults = map(mapResultColumn, results)
tableRow = [f'{participant.name} ({participant.id})'] + list(mappedResults)
tableRow = [f"{participant.name} ({participant.id})"] + list(mappedResults)
tableData.append(tableRow)
self.l.log(5, 'table data: %s', pprint.pformat(tableData))
print(tabulate(tableData, headers='firstrow', tablefmt='fancy_grid'))
self.l.log(5, "table data: %s", pprint.pformat(tableData))
print(tabulate(tableData, headers="firstrow", tablefmt="fancy_grid"))
def output(self, data: types.State4):
for idx, group in enumerate(data.groups):
if idx > 0:
print()
self.l.debug('Output for group %s', group)
self.l.debug("Output for group %s", group)
self._outputGroup(group, data.results[group])
# self.groups = self.worker.collectPersonsInGroups(data)

View File

@ -1,34 +1,28 @@
class Person:
def __init__(
self,
firstName: str,
lastName: str,
club: str,
group: str
):
def __init__(self, firstName: str, lastName: str, club: str, group: str):
self.firstName = firstName
self.lastName = lastName
self.club = club
self.group = group
def __eq__(self, o):
if not isinstance(o, Person):
False
return (
self.firstName == o.firstName and
self.lastName == o.lastName and
self.club == o.club and
self.group == o.group
self.firstName == o.firstName
and self.lastName == o.lastName
and self.club == o.club
and self.group == o.group
)
def getTuple(self):
return (self.firstName, self.lastName, self.club)
def __repr__(self):
return f'{self.firstName} {self.lastName} ({self.club}, {self.group})'
def __hash__(self):
return self.firstName.__hash__() + self.lastName.__hash__() + self.club.__hash__()
def __repr__(self):
return f"{self.firstName} {self.lastName} ({self.club}, {self.group})"
def __hash__(self):
return (
self.firstName.__hash__() + self.lastName.__hash__() + self.club.__hash__()
)

View File

@ -1,4 +1,3 @@
import solo_turnier
import csv
import os
@ -7,34 +6,32 @@ import re
from pprint import pformat
from .types import CSVResultRow as ResultRow
class CSVResultReader:
def __init__(self, fileName: str):
self.fileName = fileName
self.l = logging.getLogger('solo_turnier.reader.CSVResultReader')
self.l = logging.getLogger("solo_turnier.reader.CSVResultReader")
def readFile(self):
with open(self.fileName, 'r') as fp:
with open(self.fileName, "r") as fp:
dialect = csv.Sniffer().sniff(fp.read(1024))
fp.seek(0)
csvReader = csv.reader(fp, dialect)
rows = []
for row in csvReader:
rows.append(row)
ret = {
'header': rows[0],
'data': rows[1:]
}
self.l.log(5, 'Imported results from allresults.csv file: %s', (ret))
ret = {"header": rows[0], "data": rows[1:]}
self.l.log(5, "Imported results from allresults.csv file: %s", (ret))
return ret
def extractResult(self, entries = None) -> list[ResultRow]:
def extractResult(self, entries=None) -> list[ResultRow]:
if entries is None:
entries = self.readFile()
groupParser = solo_turnier.group.GroupParser()
classParser = solo_turnier.competition_class.CompetitionClassParser()
@ -44,40 +41,35 @@ class CSVResultReader:
competitionClass=classParser.parseClass(row[3]),
dance=row[4],
id=row[5],
firstName=row[6], lastName=row[7],
firstName=row[6],
lastName=row[7],
club=row[10],
place=row[12], placeTo=row[13],
place=row[12],
placeTo=row[13],
group=groupParser.parseClass(row[15]),
class_=classParser.parseClass(row[16])
class_=classParser.parseClass(row[16]),
)
self.l.log(5, 'Found row in CSV: %s', result)
self.l.log(5, "Found row in CSV: %s", result)
return result
ret = list(map(__processRow, entries['data']))
self.l.log(5, 'Extracted rows from CSV data: %s', ret)
ret = list(map(__processRow, entries["data"]))
self.l.log(5, "Extracted rows from CSV data: %s", ret)
return ret
class CSVExtractor:
def __init__(self):
self.l = logging.getLogger('solo_turnier.worker')
self.__groupMaps = {
'Kinder': 'Kin.',
'Junioren': 'Jun.',
'Jugend': 'Jug.'
}
self.__classMaps = {
'Newcomer': 'Newc.',
'Beginner': 'Beg.',
'Advanced': 'Adv.'
}
self.l = logging.getLogger("solo_turnier.worker")
self.__groupMaps = {"Kinder": "Kin.", "Junioren": "Jun.", "Jugend": "Jug."}
self.__classMaps = {"Newcomer": "Newc.", "Beginner": "Beg.", "Advanced": "Adv."}
def __mapGroup(self, group):
return self.__groupMaps.get(group, group)
def __mapClass(self, class_):
return self.__classMaps.get(class_, class_)
def mapCSVImport(self, imported) -> list[ResultRow]:
ret = []
@ -87,15 +79,18 @@ class CSVExtractor:
competitionClass=self.__mapClass(row[3]),
dance=row[4],
id=row[5],
firstName=row[6], lastName=row[7],
firstName=row[6],
lastName=row[7],
club=row[10],
place=row[12], placeTo=row[13],
group=self.__mapGroup(row[15]), class_=self.__mapClass(row[16])
place=row[12],
placeTo=row[13],
group=self.__mapGroup(row[15]),
class_=self.__mapClass(row[16]),
)
ret.append(result)
self.l.log(5, 'Found row in CSV: %s', result)
self.l.log(5, "Found row in CSV: %s", result)
for row in imported['data']:
for row in imported["data"]:
__processRow(row)
return ret

View File

@ -1,21 +1,23 @@
import solo_turnier.competition_class
import pytest
@pytest.fixture(params=range(9))
def fix_pureClass(request):
cases = (
('Newc', 'Newc.'),
('Newc.', 'Newc.'),
('Newcomer', 'Newc.'),
('Beg', 'Beg.'),
('Beg.', 'Beg.'),
('Beginner', 'Beg.'),
('Adv', 'Adv.'),
('Adv.', 'Adv.'),
('Advanced', 'Adv.'),
("Newc", "Newc."),
("Newc.", "Newc."),
("Newcomer", "Newc."),
("Beg", "Beg."),
("Beg.", "Beg."),
("Beginner", "Beg."),
("Adv", "Adv."),
("Adv.", "Adv."),
("Advanced", "Adv."),
)
return cases[request.param]
def test_pureClassParsing(fix_pureClass):
className = fix_pureClass[0]
expected = fix_pureClass[1]
@ -28,39 +30,43 @@ def test_pureClassParsing(fix_pureClass):
assert parser.isPureClass(className)
def test_classParsingWithPreview():
parser = solo_turnier.competition_class.CompetitionClassParser()
ret = parser.parseClass('Sichtung', True)
ret = parser.parseClass("Sichtung", True)
assert isinstance(ret, solo_turnier.competition_class.CompetitionClass)
assert str(ret) == 'Sichtung'
assert str(ret) == "Sichtung"
assert parser.isPureClass("Sichtung", True)
assert parser.isPureClass('Sichtung', True)
def test_classParsingInvalidPreview():
parser = solo_turnier.competition_class.CompetitionClassParser()
try:
parser.parseClass('Sichtung')
parser.parseClass("Sichtung")
assert False
except:
assert True
try:
parser.isPureClass('Sichtung')
parser.isPureClass("Sichtung")
assert False
except:
assert True
@pytest.fixture(params=range(4))
def fix_combinedClass(request):
cases = (
('Newc/Beg', 'Newc./Beg.'),
('Newc./Beg', 'Newc./Beg.'),
('Beginner/Adv', 'Beg./Adv.'),
('Beg/Adv', 'Beg./Adv.'),
("Newc/Beg", "Newc./Beg."),
("Newc./Beg", "Newc./Beg."),
("Beginner/Adv", "Beg./Adv."),
("Beg/Adv", "Beg./Adv."),
)
return cases[request.param]
def test_combinedClassParsing(fix_combinedClass):
className = fix_combinedClass[0]
expected = fix_combinedClass[1]

View File

@ -2,12 +2,13 @@ import solo_turnier.reader
import os
import json
def test_import():
fileName = os.path.join(os.path.dirname(__file__), 'reader', 'test.csv')
fileName = os.path.join(os.path.dirname(__file__), "reader", "test.csv")
reader = solo_turnier.reader.CSVResultReader(fileName)
ret = reader.readFile()
with open(os.path.join(os.path.dirname(__file__), 'reader', 'expected.json')) as fp:
with open(os.path.join(os.path.dirname(__file__), "reader", "expected.json")) as fp:
expected = json.load(fp)
assert ret == expected

View File

@ -1,21 +1,23 @@
import solo_turnier.group
import pytest
@pytest.fixture(params=range(9))
def fix_pureClass(request):
cases = (
('Kin', 'Kin.'),
('Kin.', 'Kin.'),
('Kinder', 'Kin.'),
('Jun', 'Jun.'),
('Jun.', 'Jun.'),
('Junioren', 'Jun.'),
('Jug', 'Jug.'),
('Jug.', 'Jug.'),
('Jugend', 'Jug.'),
("Kin", "Kin."),
("Kin.", "Kin."),
("Kinder", "Kin."),
("Jun", "Jun."),
("Jun.", "Jun."),
("Junioren", "Jun."),
("Jug", "Jug."),
("Jug.", "Jug."),
("Jugend", "Jug."),
)
return cases[request.param]
def test_pureClassParsing(fix_pureClass):
className = fix_pureClass[0]
expected = fix_pureClass[1]
@ -28,16 +30,18 @@ def test_pureClassParsing(fix_pureClass):
assert parser.isPureClass(className)
@pytest.fixture(params=range(4))
def fix_combinedClass(request):
cases = (
('Kin/Jun', 'Kin./Jun.'),
('Kin./Jun', 'Kin./Jun.'),
('Junioren/Jug', 'Jun./Jug.'),
('Jun/Jug', 'Jun./Jug.'),
("Kin/Jun", "Kin./Jun."),
("Kin./Jun", "Kin./Jun."),
("Junioren/Jug", "Jun./Jug."),
("Jun/Jug", "Jun./Jug."),
)
return cases[request.param]
def test_combinedClassParsing(fix_combinedClass):
className = fix_combinedClass[0]
expected = fix_combinedClass[1]

View File

@ -1,17 +1,17 @@
import os
import solo_turnier.html_locator
def test_fetchLocationCandidates():
folder = os.path.join(os.path.dirname(__file__), 'html_locator', 'export')
folder = os.path.join(os.path.dirname(__file__), "html_locator", "export")
relFolder = os.path.relpath(folder)
locator = solo_turnier.html_locator.HtmlLocator()
candidates = locator.findCandidates(relFolder)
expected = [
'solo_turnier/tests/html_locator/export/2-bar/erg.htm',
'solo_turnier/tests/html_locator/export/3-baz/erg.htm',
'solo_turnier/tests/html_locator/export/3-baz/subfolder/4-baz/erg.htm'
"solo_turnier/tests/html_locator/export/2-bar/erg.htm",
"solo_turnier/tests/html_locator/export/3-baz/erg.htm",
"solo_turnier/tests/html_locator/export/3-baz/subfolder/4-baz/erg.htm",
]
assert set(candidates) == set(expected)

View File

@ -4,20 +4,22 @@ import json
import solo_turnier.html_parser
@pytest.fixture(scope='module', params=range(2))
@pytest.fixture(scope="module", params=range(2))
def dataProviderHtmlParser(request):
variant = str(request.param+1)
dir = os.path.join(os.path.dirname(__file__), 'html_parser', 'erg', variant)
htmlFile = os.path.join(dir, 'erg.htm')
jsonFile = os.path.join(dir, 'expected.json')
with open(htmlFile, 'r') as fp:
variant = str(request.param + 1)
dir = os.path.join(os.path.dirname(__file__), "html_parser", "erg", variant)
htmlFile = os.path.join(dir, "erg.htm")
jsonFile = os.path.join(dir, "expected.json")
with open(htmlFile, "r") as fp:
html = fp.read()
with open(jsonFile, 'r') as fp:
with open(jsonFile, "r") as fp:
jsonContent = json.load(fp)
return (html, jsonContent)
def test_extractDataFromHtml(dataProviderHtmlParser):
htmlString = dataProviderHtmlParser[0]
expected = dataProviderHtmlParser[1]
@ -29,67 +31,71 @@ def test_extractDataFromHtml(dataProviderHtmlParser):
for i in actualResult.participants:
participants[i] = actualResult.participants[i].__dict__
assert actualResult.title == expected['title']
assert participants == expected['participants']
assert actualResult.title == expected["title"]
assert participants == expected["participants"]
@pytest.fixture(params=range(6))
def fixture_guessDataFromTitle(request):
cases = {
'09.07.2022 - ETW, Solos Jun. Beginner Jive': {
'class_': 'Beg.',
'dance': 'Jive',
'group': 'Jun.'
"09.07.2022 - ETW, Solos Jun. Beginner Jive": {
"class_": "Beg.",
"dance": "Jive",
"group": "Jun.",
},
'09.07.2022 - ETW, Solos Jun. Newc./Beg. Rumba': {
'class_': 'Newc./Beg.',
'dance': 'Rumba',
'group': 'Jun.'
"09.07.2022 - ETW, Solos Jun. Newc./Beg. Rumba": {
"class_": "Newc./Beg.",
"dance": "Rumba",
"group": "Jun.",
},
'09.07.2022 - ETW, Solos Kin./Jun. Beginner Cha Cha': {
'class_': 'Beg.',
'dance': 'Cha Cha',
'group': 'Kin./Jun.'
"09.07.2022 - ETW, Solos Kin./Jun. Beginner Cha Cha": {
"class_": "Beg.",
"dance": "Cha Cha",
"group": "Kin./Jun.",
},
'09.07.2022 - ETW, Solos Kin. Newcomer Samba': {
'class_': 'Newc.',
'dance': 'Samba',
'group': 'Kin.'
"09.07.2022 - ETW, Solos Kin. Newcomer Samba": {
"class_": "Newc.",
"dance": "Samba",
"group": "Kin.",
},
'09.07.2022 - ETW, Solos Jugend Beg./Adv. Wiener Walzer': {
'class_': 'Beg./Adv.',
'dance': 'Wiener Walzer',
'group': 'Jug.'
"09.07.2022 - ETW, Solos Jugend Beg./Adv. Wiener Walzer": {
"class_": "Beg./Adv.",
"dance": "Wiener Walzer",
"group": "Jug.",
},
'09.07.2022 - ETW, Solos Jugend Sichtung Wiener Walzer': {
'class_': 'Sichtung',
'dance': 'Wiener Walzer',
'group': 'Jug.'
"09.07.2022 - ETW, Solos Jugend Sichtung Wiener Walzer": {
"class_": "Sichtung",
"dance": "Wiener Walzer",
"group": "Jug.",
},
}
keys = list(cases.keys())
key = keys[request.param]
return (key, cases[key])
def test_guessDataFromTitle(fixture_guessDataFromTitle):
parser = solo_turnier.html_parser.HtmlParser('')
parser = solo_turnier.html_parser.HtmlParser("")
ret = parser.guessDataFromHtmlTitle(fixture_guessDataFromTitle[0])
assert ret == fixture_guessDataFromTitle[1]
@pytest.fixture(params=range(1))
def fixture_parsePreparationResult(request):
variant = str(request.param+1)
dir = os.path.join(os.path.dirname(__file__), 'html_parser', 'tabges', variant)
htmlFile = os.path.join(dir, 'tabges.htm')
jsonFile = os.path.join(dir, 'expected.json')
with open(htmlFile, 'r') as fp:
variant = str(request.param + 1)
dir = os.path.join(os.path.dirname(__file__), "html_parser", "tabges", variant)
htmlFile = os.path.join(dir, "tabges.htm")
jsonFile = os.path.join(dir, "expected.json")
with open(htmlFile, "r") as fp:
html = fp.read()
with open(jsonFile, 'r') as fp:
with open(jsonFile, "r") as fp:
jsonContent = json.load(fp)
return (html, jsonContent)
def test_parsePreparationResult(fixture_parsePreparationResult):
html = fixture_parsePreparationResult[0]
jsonContent = fixture_parsePreparationResult[1]
@ -99,25 +105,27 @@ def test_parsePreparationResult(fixture_parsePreparationResult):
assert ret == jsonContent
@pytest.fixture(params=range(1))
def fixture_cleanPreparationImport(request):
variant = str(request.param+1)
dir = os.path.join(os.path.dirname(__file__), 'html_parser', 'tabges', variant)
srcFile = os.path.join(dir, 'expected.json')
expectedFile = os.path.join(dir, 'cleaned.json')
with open(srcFile, 'r') as fp:
variant = str(request.param + 1)
dir = os.path.join(os.path.dirname(__file__), "html_parser", "tabges", variant)
srcFile = os.path.join(dir, "expected.json")
expectedFile = os.path.join(dir, "cleaned.json")
with open(srcFile, "r") as fp:
source = json.load(fp)
with open(expectedFile, 'r') as fp:
with open(expectedFile, "r") as fp:
expected = json.load(fp)
return (source, expected)
def test_cleanPreparationImport(fixture_cleanPreparationImport):
src = fixture_cleanPreparationImport[0]
expected = fixture_cleanPreparationImport[1]
parser = solo_turnier.html_parser.HtmlParser('')
parser = solo_turnier.html_parser.HtmlParser("")
parser.cleanPreparationRoundImport(src)
assert src == expected

View File

@ -1,10 +1,11 @@
import pytest
import solo_turnier.types as types
def test_HtmlPreviewParticipant_eq():
name = 'Max Mustermann'
name = "Max Mustermann"
id = 123
group = 'Kin'
group = "Kin"
participant = types.HtmlPreviewParticipant(name, id, group)
l = []
@ -13,6 +14,6 @@ def test_HtmlPreviewParticipant_eq():
assert participant in l
assert types.HtmlPreviewParticipant(name, id, group) in l
assert types.HtmlPreviewParticipant('Maxime Musterfrau', id, group) not in l
assert types.HtmlPreviewParticipant("Maxime Musterfrau", id, group) not in l
assert types.HtmlPreviewParticipant(name, 234, group) not in l
assert types.HtmlPreviewParticipant(name, id, 'Jun') not in l
assert types.HtmlPreviewParticipant(name, id, "Jun") not in l

View File

@ -4,178 +4,298 @@ import json
import pytest
import pytest_mock
def __importJSONData(name):
path = os.path.join(os.path.dirname(__file__), 'worker', name)
with open(path, 'r') as fp:
path = os.path.join(os.path.dirname(__file__), "worker", name)
with open(path, "r") as fp:
return json.load(fp)
@pytest.fixture
def fixture_csvExtractor():
data = __importJSONData('csvImport.json')
expected = __importJSONData('csvImportResult.json')
data = __importJSONData("csvImport.json")
expected = __importJSONData("csvImportResult.json")
return (data, expected)
def test_csvExtractor(fixture_csvExtractor):
extractor = worker.CSVExtractor()
mapped = extractor.mapCSVImport(fixture_csvExtractor[0])
assert len(mapped) == len(fixture_csvExtractor[1])
for i,elem in enumerate(fixture_csvExtractor[1]):
for i, elem in enumerate(fixture_csvExtractor[1]):
assert mapped[i].__dict__ == elem
def test_extractPersonFromRow():
row = worker.ResultRow('Max', 'Mustermann', 'TSC Entenhausen', '2', 'Kin', 'Adv.', 'Rumba', '2', '2', 'Kin.', 'Beg./Adv.')
row = worker.ResultRow(
"Max",
"Mustermann",
"TSC Entenhausen",
"2",
"Kin",
"Adv.",
"Rumba",
"2",
"2",
"Kin.",
"Beg./Adv.",
)
person = worker.ResultPerson.extractFromResultRow(row)
expected = {
'firstName': 'Max',
'lastName': 'Mustermann',
'name': 'Max Mustermann',
'club': 'TSC Entenhausen',
'id': None,
'group': None
"firstName": "Max",
"lastName": "Mustermann",
"name": "Max Mustermann",
"club": "TSC Entenhausen",
"id": None,
"group": None,
}
assert person.__dict__ == expected
def test_extractCompetitionFromRow():
row = worker.ResultRow('Max', 'Mustermann', 'TSC Entenhausen', '2', 'Kin', 'Adv.', 'Rumba', '2', '2', 'Kin.', 'Beg./Adv.')
row = worker.ResultRow(
"Max",
"Mustermann",
"TSC Entenhausen",
"2",
"Kin",
"Adv.",
"Rumba",
"2",
"2",
"Kin.",
"Beg./Adv.",
)
competition = worker.CompetitionResult.extractFromResultRow(row)
expected = {
'dance': 'Rumba',
'class_': 'Adv.',
'group': 'Kin',
'place': '2',
'placeTo': '2',
'id': 2,
'finalist': None,
'competitionGroup': 'Kin.',
'competitionClass': 'Beg./Adv.'
"dance": "Rumba",
"class_": "Adv.",
"group": "Kin",
"place": "2",
"placeTo": "2",
"id": 2,
"finalist": None,
"competitionGroup": "Kin.",
"competitionClass": "Beg./Adv.",
}
assert competition.__dict__ == expected
def test_combineRowsByPerson():
rows = [
worker.ResultRow('Max', 'Mustermann', 'TSC Entenhausen', '2', 'Kin', 'Adv.', 'Cha Cha', '-', '-', 'Kin.', 'Adv.'),
worker.ResultRow('Max', 'Mustermann', 'TSC Entenhausen', '2', 'Kin', 'Adv.', 'Rumba', '2', '2', 'Kin.', 'Adv.'),
worker.ResultRow('Max', 'Mustermann', 'TSC Entenhausen', '2', 'Kin', 'Beg.', 'Jive', '1', '1', 'Kin.', 'Beg.'),
worker.ResultRow('Maxime', 'Musterfrau', '1. SC Entenhausen', '1', 'Kin', 'Adv.', 'Rumba', '1', '1', 'Kin.', 'Adv.')
worker.ResultRow(
"Max",
"Mustermann",
"TSC Entenhausen",
"2",
"Kin",
"Adv.",
"Cha Cha",
"-",
"-",
"Kin.",
"Adv.",
),
worker.ResultRow(
"Max",
"Mustermann",
"TSC Entenhausen",
"2",
"Kin",
"Adv.",
"Rumba",
"2",
"2",
"Kin.",
"Adv.",
),
worker.ResultRow(
"Max",
"Mustermann",
"TSC Entenhausen",
"2",
"Kin",
"Beg.",
"Jive",
"1",
"1",
"Kin.",
"Beg.",
),
worker.ResultRow(
"Maxime",
"Musterfrau",
"1. SC Entenhausen",
"1",
"Kin",
"Adv.",
"Rumba",
"1",
"1",
"Kin.",
"Adv.",
),
]
dataWorker = worker.DataWorker()
result = dataWorker.combineRowsByPerson(rows)
expected = {
worker.ResultPerson('Max', 'Mustermann', 'TSC Entenhausen'): [
worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '2', '2', '2', 'Kin.', 'Adv.'),
worker.CompetitionResult('Jive', 'Kin', 'Beg.', '1', '1', '2', 'Kin.', 'Beg.')
worker.ResultPerson("Max", "Mustermann", "TSC Entenhausen"): [
worker.CompetitionResult(
"Rumba", "Kin", "Adv.", "2", "2", "2", "Kin.", "Adv."
),
worker.CompetitionResult(
"Jive", "Kin", "Beg.", "1", "1", "2", "Kin.", "Beg."
),
],
worker.ResultPerson("Maxime", "Musterfrau", "1. SC Entenhausen"): [
worker.CompetitionResult(
"Rumba", "Kin", "Adv.", "1", "1", "1", "Kin.", "Adv."
)
],
worker.ResultPerson('Maxime', 'Musterfrau', '1. SC Entenhausen'): [
worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '1', '1', '1', 'Kin.', 'Adv.')
]
}
assert result == expected
def test_checkUniqueIds_True():
person1 = worker.ResultPerson('Max', 'Mustermann', 'TSC Entenhausen')
person2 = worker.ResultPerson('Maxime', 'Musterfrau', '1. SC Entenhausen')
person1 = worker.ResultPerson("Max", "Mustermann", "TSC Entenhausen")
person2 = worker.ResultPerson("Maxime", "Musterfrau", "1. SC Entenhausen")
data = {
person1: [
worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
worker.CompetitionResult('Jive', 'Kin', 'Beg.', '1', '1', 2, 'Kin.', 'Beg.')
worker.CompetitionResult(
"Rumba", "Kin", "Adv.", "2", "2", 2, "Kin.", "Adv."
),
worker.CompetitionResult(
"Jive", "Kin", "Beg.", "1", "1", 2, "Kin.", "Beg."
),
],
person2: [
worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '1', '1', 1, 'Kin.', 'Adv.')
]
worker.CompetitionResult(
"Rumba", "Kin", "Adv.", "1", "1", 1, "Kin.", "Adv."
)
],
}
dataWorker = worker.DataWorker()
assert dataWorker.checkUniqueIds(data) == True
assert person1.id == 2
assert person2.id == 1
def test_checkUniqueIds_False():
person1 = worker.ResultPerson('Max', 'Mustermann', 'TSC Entenhausen')
person2 = worker.ResultPerson('Maxime', 'Musterfrau', '1. SC Entenhausen')
person1 = worker.ResultPerson("Max", "Mustermann", "TSC Entenhausen")
person2 = worker.ResultPerson("Maxime", "Musterfrau", "1. SC Entenhausen")
data = {
person1: [
worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
worker.CompetitionResult('Jive', 'Kin', 'Beg.', '1', '1', 3, 'Kin.', 'Beg.')
worker.CompetitionResult(
"Rumba", "Kin", "Adv.", "2", "2", 2, "Kin.", "Adv."
),
worker.CompetitionResult(
"Jive", "Kin", "Beg.", "1", "1", 3, "Kin.", "Beg."
),
],
person2: [
worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '1', '1', 1, 'Kin.', 'Adv.')
]
worker.CompetitionResult(
"Rumba", "Kin", "Adv.", "1", "1", 1, "Kin.", "Adv."
)
],
}
dataWorker = worker.DataWorker()
assert dataWorker.checkUniqueIds(data) == False
assert person1.id == None
assert person2.id == 1
@pytest.fixture(params=range(5))
def fixture_consolidateGroups(request):
person1 = worker.ResultPerson('Max 1', 'Mustermann', 'TSC Entenhausen')
person2 = worker.ResultPerson('Max 2', 'Mustermann', 'TSC Entenhausen')
person3 = worker.ResultPerson('Max 3', 'Mustermann', 'TSC Entenhausen')
person4 = worker.ResultPerson('Max 4', 'Mustermann', 'TSC Entenhausen')
person1 = worker.ResultPerson("Max 1", "Mustermann", "TSC Entenhausen")
person2 = worker.ResultPerson("Max 2", "Mustermann", "TSC Entenhausen")
person3 = worker.ResultPerson("Max 3", "Mustermann", "TSC Entenhausen")
person4 = worker.ResultPerson("Max 4", "Mustermann", "TSC Entenhausen")
# persons = (person1, person2, person3, person4)
dict1 = {
person1: [
worker.CompetitionResult('Rumba', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
worker.CompetitionResult('Jive', 'Kin.', 'Beg.', '1', '1', 3, 'Kin.', 'Beg.')
worker.CompetitionResult(
"Rumba", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
),
worker.CompetitionResult(
"Jive", "Kin.", "Beg.", "1", "1", 3, "Kin.", "Beg."
),
]
}
dict2 = {
person2: [
worker.CompetitionResult('Rumba', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
worker.CompetitionResult('Jive', 'Kin./Jun.', 'Beg.', '1', '1', 3, 'Kin./Jun.', 'Beg.')
worker.CompetitionResult(
"Rumba", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
),
worker.CompetitionResult(
"Jive", "Kin./Jun.", "Beg.", "1", "1", 3, "Kin./Jun.", "Beg."
),
]
}
dict3 = {
person3: [
worker.CompetitionResult('Rumba', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.')
worker.CompetitionResult(
"Rumba", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
)
]
}
dict4 = {
person4: [
worker.CompetitionResult('Rumba', 'Kin./Jun.', 'Adv.', '2', '2', 2, 'Kin./Jun.', 'Adv.')
worker.CompetitionResult(
"Rumba", "Kin./Jun.", "Adv.", "2", "2", 2, "Kin./Jun.", "Adv."
)
]
}
dict5 = {
person4: [
worker.CompetitionResult('Rumba', 'Kin./Jun.', 'Adv.', '2', '2', 2, 'Kin./Jun.', 'Adv.'),
worker.CompetitionResult('Cha Cha', 'Jun./Jug.', 'Beg.', '3', '4', 2, 'Jun./Jug.', 'Beg.')
worker.CompetitionResult(
"Rumba", "Kin./Jun.", "Adv.", "2", "2", 2, "Kin./Jun.", "Adv."
),
worker.CompetitionResult(
"Cha Cha", "Jun./Jug.", "Beg.", "3", "4", 2, "Jun./Jug.", "Beg."
),
]
}
cases = (
(dict1|dict3, (True, False), {}),
(dict1|dict2|dict3, (True, True), {}),
(dict4, (False, False), {person4: 'Kin./Jun.'}),
(dict1|dict2|dict3|dict4, (False, True), {person4: 'Kin./Jun.'}),
(dict5, (True, True), {person4: 'Jun.'}),
(dict1 | dict3, (True, False), {}),
(dict1 | dict2 | dict3, (True, True), {}),
(dict4, (False, False), {person4: "Kin./Jun."}),
(dict1 | dict2 | dict3 | dict4, (False, True), {person4: "Kin./Jun."}),
(dict5, (True, True), {person4: "Jun."}),
)
return cases[request.param]
@pytest.fixture(params=range(2))
def fixture_consolidateGroups_fail(request, fixture_consolidateGroups):
person = worker.ResultPerson('Max 5', 'Mustermann', 'TSC Entenhausen')
person = worker.ResultPerson("Max 5", "Mustermann", "TSC Entenhausen")
dict1 = {
person: [
worker.CompetitionResult('Rumba', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
worker.CompetitionResult('Jive', 'Jun.', 'Beg.', '1', '1', 3, 'Jun.', 'Adv.')
worker.CompetitionResult(
"Rumba", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
),
worker.CompetitionResult(
"Jive", "Jun.", "Beg.", "1", "1", 3, "Jun.", "Adv."
),
]
}
dict2 = {
person: [
worker.CompetitionResult('Rumba', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
worker.CompetitionResult('Jive', 'Hgr', 'Beg.', '1', '1', 3, 'Hgr', 'Adv.')
worker.CompetitionResult(
"Rumba", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
),
worker.CompetitionResult("Jive", "Hgr", "Beg.", "1", "1", 3, "Hgr", "Adv."),
]
}
cases = (
dict1 | fixture_consolidateGroups[0],
dict2 | fixture_consolidateGroups[0]
)
cases = (dict1 | fixture_consolidateGroups[0], dict2 | fixture_consolidateGroups[0])
return cases[request.param]
def test_consolidateGroups(fixture_consolidateGroups):
data = fixture_consolidateGroups[0]
dataWorker = worker.DataWorker()
@ -183,7 +303,8 @@ def test_consolidateGroups(fixture_consolidateGroups):
assert dataWorker.consolidateGroups(data) == fixture_consolidateGroups[1]
for person in data:
assert person.group == fixture_consolidateGroups[2].get(person, 'Kin.')
assert person.group == fixture_consolidateGroups[2].get(person, "Kin.")
def test_consolidateGroups_failing(fixture_consolidateGroups_fail):
data = fixture_consolidateGroups_fail
@ -192,105 +313,143 @@ def test_consolidateGroups_failing(fixture_consolidateGroups_fail):
with pytest.raises(Exception):
dataWorker.consolidateGroups(data)
def test_createHtmlLUT(mocker):
mock = mocker.patch('solo_turnier.html_parser.HtmlParser.guessDataFromHtmlTitle')
mock.side_effect= [
{'group': 'group1', 'class_': 'class1', 'dance': 'dance1'},
{'group': 'group2', 'class_': 'class2', 'dance': 'dance2'},
{'group': 'group3', 'class_': 'class3', 'dance': 'dance3'},
mock = mocker.patch("solo_turnier.html_parser.HtmlParser.guessDataFromHtmlTitle")
mock.side_effect = [
{"group": "group1", "class_": "class1", "dance": "dance1"},
{"group": "group2", "class_": "class2", "dance": "dance2"},
{"group": "group3", "class_": "class3", "dance": "dance3"},
]
importMock1 = mocker.patch('solo_turnier.html_parser.HtmlImport')
importMock2 = mocker.patch('solo_turnier.html_parser.HtmlImport')
importMock3 = mocker.patch('solo_turnier.html_parser.HtmlImport')
importMock1.title = 'Fake title 1'
importMock2.title = 'Fake title 2'
importMock3.title = 'Fake title 3'
importMock1 = mocker.patch("solo_turnier.html_parser.HtmlImport")
importMock2 = mocker.patch("solo_turnier.html_parser.HtmlImport")
importMock3 = mocker.patch("solo_turnier.html_parser.HtmlImport")
importMock1.title = "Fake title 1"
importMock2.title = "Fake title 2"
importMock3.title = "Fake title 3"
dataWorker = worker.DataWorker()
structure = dataWorker._createHtmlLUT([importMock1, importMock2, importMock3])
expected = {
('group1', 'class1', 'dance1'): importMock1,
('group2', 'class2', 'dance2'): importMock2,
('group3', 'class3', 'dance3'): importMock3,
("group1", "class1", "dance1"): importMock1,
("group2", "class2", "dance2"): importMock2,
("group3", "class3", "dance3"): importMock3,
}
assert expected == structure
def test_mergeHtmlData(mocker):
person1 = worker.ResultPerson('Max 1', 'Mustermann', 'TSC Entenhausen')
person2 = worker.ResultPerson('Max 2', 'Mustermann', 'TSC Entenhausen')
person3 = worker.ResultPerson('Max 3', 'Mustermann', 'TSC Entenhausen')
person4 = worker.ResultPerson('Max 4', 'Mustermann', 'TSC Entenhausen')
person1 = worker.ResultPerson("Max 1", "Mustermann", "TSC Entenhausen")
person2 = worker.ResultPerson("Max 2", "Mustermann", "TSC Entenhausen")
person3 = worker.ResultPerson("Max 3", "Mustermann", "TSC Entenhausen")
person4 = worker.ResultPerson("Max 4", "Mustermann", "TSC Entenhausen")
data = {
person1: [
worker.CompetitionResult('Rumba', 'Kin.', 'Beg.', '1', '1', 1, 'Kin./Jun.', 'Beg.'),
worker.CompetitionResult('Cha Cha', 'Kin.', 'Adv.', '1', '1', 1, 'Kin.', 'Adv.'),
worker.CompetitionResult('Jive', 'Kin.', 'Beg.', '1', '2', 1, 'Kin.', 'Beg.'),
worker.CompetitionResult('Langs. Walzer', 'Kin.', 'Beg.', '1', '1', 1, 'Kin.', 'Newc./Beg.'),
worker.CompetitionResult(
"Rumba", "Kin.", "Beg.", "1", "1", 1, "Kin./Jun.", "Beg."
),
worker.CompetitionResult(
"Cha Cha", "Kin.", "Adv.", "1", "1", 1, "Kin.", "Adv."
),
worker.CompetitionResult(
"Jive", "Kin.", "Beg.", "1", "2", 1, "Kin.", "Beg."
),
worker.CompetitionResult(
"Langs. Walzer", "Kin.", "Beg.", "1", "1", 1, "Kin.", "Newc./Beg."
),
],
person2: [
worker.CompetitionResult('Rumba', 'Kin.', 'Beg.', '2', '2', 2, 'Kin./Jun.', 'Beg.'),
worker.CompetitionResult('Cha Cha', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
worker.CompetitionResult('Jive', 'Kin.', 'Beg.', '1', '2', 2, 'Kin.', 'Beg.'),
worker.CompetitionResult('Langs. Walzer', 'Kin.', 'Newc.', '1', '1', 2, 'Kin.', 'Newc./Beg.'),
worker.CompetitionResult(
"Rumba", "Kin.", "Beg.", "2", "2", 2, "Kin./Jun.", "Beg."
),
worker.CompetitionResult(
"Cha Cha", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
),
worker.CompetitionResult(
"Jive", "Kin.", "Beg.", "1", "2", 2, "Kin.", "Beg."
),
worker.CompetitionResult(
"Langs. Walzer", "Kin.", "Newc.", "1", "1", 2, "Kin.", "Newc./Beg."
),
],
person3: [
worker.CompetitionResult('Rumba', 'Jun.', 'Beg.', '1', '1', 3, 'Kin./Jun.', 'Beg.'),
worker.CompetitionResult(
"Rumba", "Jun.", "Beg.", "1", "1", 3, "Kin./Jun.", "Beg."
),
# worker.CompetitionResult('Cha Cha', 'Jun.', 'Adv.', '1', '1', 3, 'Kin.', 'Adv.'),
# worker.CompetitionResult('Jive', 'Jun.', 'Beg.', '2', '2', 3, 'Kin.', 'Beg.'),
# worker.CompetitionResult('Langs. Walzer', 'Jun.', 'Newc./Beg.', '1', '1', 3, 'Kin.', 'Beg.'),
],
person4: [
worker.CompetitionResult('Rumba', 'Kin.', 'Beg.', '3', '3', 4, 'Kin./Jun.', 'Beg.'),
worker.CompetitionResult(
"Rumba", "Kin.", "Beg.", "3", "3", 4, "Kin./Jun.", "Beg."
),
# worker.CompetitionResult('Cha Cha', 'Kin.', 'Adv.', '1', '1', 4, 'Kin.', 'Adv.'),
# worker.CompetitionResult('Jive', 'Kin.', 'Beg.', '2', '2', 4, 'Kin.', 'Beg.'),
# worker.CompetitionResult('Langs. Walzer', 'Kin.', 'Newc./Beg.', '1', '1', 4, 'Kin.', 'Beg.'),
],
}
htmlParticipant1Dance1 = html_parser.HtmlParticipant('Max 1 Mustermann', '1.', True)
htmlParticipant1Dance2 = html_parser.HtmlParticipant('Max 1 Mustermann', '1.', True)
htmlParticipant1Dance3 = html_parser.HtmlParticipant('Max 1 Mustermann', '1.-2.', True)
htmlParticipant1Dance4 = html_parser.HtmlParticipant('Max 1 Mustermann', '1.', True)
htmlParticipant2Dance1 = html_parser.HtmlParticipant('Max 2 Mustermann', '2.', True)
htmlParticipant2Dance2 = html_parser.HtmlParticipant('Max 2 Mustermann', '2.', True)
htmlParticipant2Dance3 = html_parser.HtmlParticipant('Max 2 Mustermann', '1.-2.', True)
htmlParticipant2Dance4 = html_parser.HtmlParticipant('Max 2 Mustermann', '1.', True)
htmlParticipant3Dance1 = html_parser.HtmlParticipant('Max 3 Mustermann', '1.', True)
htmlParticipant1Dance1 = html_parser.HtmlParticipant("Max 1 Mustermann", "1.", True)
htmlParticipant1Dance2 = html_parser.HtmlParticipant("Max 1 Mustermann", "1.", True)
htmlParticipant1Dance3 = html_parser.HtmlParticipant(
"Max 1 Mustermann", "1.-2.", True
)
htmlParticipant1Dance4 = html_parser.HtmlParticipant("Max 1 Mustermann", "1.", True)
htmlParticipant4Dance1 = html_parser.HtmlParticipant('Max 4 Mustermann', '3.', False)
htmlParticipant2Dance1 = html_parser.HtmlParticipant("Max 2 Mustermann", "2.", True)
htmlParticipant2Dance2 = html_parser.HtmlParticipant("Max 2 Mustermann", "2.", True)
htmlParticipant2Dance3 = html_parser.HtmlParticipant(
"Max 2 Mustermann", "1.-2.", True
)
htmlParticipant2Dance4 = html_parser.HtmlParticipant("Max 2 Mustermann", "1.", True)
htmlParticipant3Dance1 = html_parser.HtmlParticipant("Max 3 Mustermann", "1.", True)
htmlParticipant4Dance1 = html_parser.HtmlParticipant(
"Max 4 Mustermann", "3.", False
)
htmlParticipantsDance1 = {
'1': htmlParticipant1Dance1,
'2': htmlParticipant2Dance1,
'3': htmlParticipant3Dance1,
'4': htmlParticipant4Dance1
"1": htmlParticipant1Dance1,
"2": htmlParticipant2Dance1,
"3": htmlParticipant3Dance1,
"4": htmlParticipant4Dance1,
}
htmlParticipantsDance2 = {
'1': htmlParticipant1Dance2,
'2': htmlParticipant2Dance2,
"1": htmlParticipant1Dance2,
"2": htmlParticipant2Dance2,
}
htmlParticipantsDance3 = {
'1': htmlParticipant1Dance3,
'2': htmlParticipant2Dance3,
"1": htmlParticipant1Dance3,
"2": htmlParticipant2Dance3,
}
htmlParticipantsDance4 = {
'1': htmlParticipant1Dance4,
'2': htmlParticipant2Dance4,
"1": htmlParticipant1Dance4,
"2": htmlParticipant2Dance4,
}
htmlCompetition1 = html_parser.HtmlImport('ETW, Solos Kin./Jun. Beginner Rumba', htmlParticipantsDance1)
htmlCompetition2 = html_parser.HtmlImport('ETW, Solos Kin. Advanced Cha Cha', htmlParticipantsDance2)
htmlCompetition3 = html_parser.HtmlImport('ETW, Solos Kinder Beginner Jive', htmlParticipantsDance3)
htmlCompetition4 = html_parser.HtmlImport('ETW, Solos Kin. Newc./Beg. Langs. Walzer', htmlParticipantsDance4)
htmlCompetition1 = html_parser.HtmlImport(
"ETW, Solos Kin./Jun. Beginner Rumba", htmlParticipantsDance1
)
htmlCompetition2 = html_parser.HtmlImport(
"ETW, Solos Kin. Advanced Cha Cha", htmlParticipantsDance2
)
htmlCompetition3 = html_parser.HtmlImport(
"ETW, Solos Kinder Beginner Jive", htmlParticipantsDance3
)
htmlCompetition4 = html_parser.HtmlImport(
"ETW, Solos Kin. Newc./Beg. Langs. Walzer", htmlParticipantsDance4
)
dataWorker = worker.DataWorker()
dataWorker.mergeHtmlData(data, [htmlCompetition1, htmlCompetition2, htmlCompetition3, htmlCompetition4])
dataWorker.mergeHtmlData(
data, [htmlCompetition1, htmlCompetition2, htmlCompetition3, htmlCompetition4]
)
person1Finalist = [c.finalist for c in data[person1]]
person2Finalist = [c.finalist for c in data[person2]]
@ -306,33 +465,59 @@ def test_mergeHtmlData(mocker):
person1: [True, True, True, True],
person2: [True, True, True, True],
person3: [True],
person4: [False],
person4: [False],
}
assert finalists == expectedFinalists
@pytest.fixture(params=range(4))
def fixture_getAllDancesInCompetition(request, mocker):
def mockCompetition(comp):
def mockUser():
return mocker.patch('solo_turnier.worker.ResultPerson')
return mocker.patch("solo_turnier.worker.ResultPerson")
def mockDances(dances):
def mockDance(name):
mock = mocker.patch('solo_turnier.worker.CompetitionResult')
mock = mocker.patch("solo_turnier.worker.CompetitionResult")
mock.dance = name
return mock
return [mockDance(d) for d in dances]
return {mockUser(): mockDances(dances) for dances in comp}
cases = (
([['Samba']], ['Samba']),
([['Samba', 'Rumba'], ['Cha Cha']], ['Samba', 'Cha Cha', 'Rumba']),
([['Samba', 'Rumba'], ['Cha Cha', 'Tango', 'Langs. Walzer']], ['Samba', 'Cha Cha', 'Rumba', 'Langs. Walzer', 'Tango']),
([['Cha Cha', 'Rumba', 'Jive'], ['Quickstep', 'Tango', 'Wiener Walzer', 'Langs. Walzer'], ['Slowfox', 'Langs. Walzer', 'Paso Doble', 'Samba']], ['Samba', 'Cha Cha', 'Rumba', 'Paso Doble', 'Jive', 'Langs. Walzer', 'Tango', 'Wiener Walzer', 'Slowfox', 'Quickstep'])
([["Samba"]], ["Samba"]),
([["Samba", "Rumba"], ["Cha Cha"]], ["Samba", "Cha Cha", "Rumba"]),
(
[["Samba", "Rumba"], ["Cha Cha", "Tango", "Langs. Walzer"]],
["Samba", "Cha Cha", "Rumba", "Langs. Walzer", "Tango"],
),
(
[
["Cha Cha", "Rumba", "Jive"],
["Quickstep", "Tango", "Wiener Walzer", "Langs. Walzer"],
["Slowfox", "Langs. Walzer", "Paso Doble", "Samba"],
],
[
"Samba",
"Cha Cha",
"Rumba",
"Paso Doble",
"Jive",
"Langs. Walzer",
"Tango",
"Wiener Walzer",
"Slowfox",
"Quickstep",
],
),
)
case = cases[request.param]
return (mockCompetition(case[0]), case[1])
def test_getAllDancesInCompetitions(fixture_getAllDancesInCompetition):
print(fixture_getAllDancesInCompetition)
data = fixture_getAllDancesInCompetition[0]
@ -340,57 +525,76 @@ def test_getAllDancesInCompetitions(fixture_getAllDancesInCompetition):
ret = dataWorker.getAllDancesInCompetitions(data)
assert ret == fixture_getAllDancesInCompetition[1]
def test_collectPersonsInGroups(mocker):
def mockPerson(group):
mock = mocker.patch('solo_turnier.worker.ResultPerson')
mock = mocker.patch("solo_turnier.worker.ResultPerson")
mock.group = group
return mock
persons = (
mockPerson('Kin.'), mockPerson('Kin.'), mockPerson('Jun.'),
mockPerson('Kin.'), mockPerson(None), mockPerson('Jug.'),
mockPerson(None), mockPerson('Kin./Jun.'), mockPerson('Jun.')
mockPerson("Kin."),
mockPerson("Kin."),
mockPerson("Jun."),
mockPerson("Kin."),
mockPerson(None),
mockPerson("Jug."),
mockPerson(None),
mockPerson("Kin./Jun."),
mockPerson("Jun."),
)
data = {p: [] for p in persons}
dataWorker = worker.DataWorker()
groups = dataWorker.collectPersonsInGroups(data)
assert groups['Kin.'] == [persons[0], persons[1], persons[3]]
assert groups['Jun.'] == [persons[2], persons[8]]
assert groups['Jug.'] == [persons[5]]
assert groups['Sonst'] == [persons[4], persons[6], persons[7]]
assert groups["Kin."] == [persons[0], persons[1], persons[3]]
assert groups["Jun."] == [persons[2], persons[8]]
assert groups["Jug."] == [persons[5]]
assert groups["Sonst"] == [persons[4], persons[6], persons[7]]
def test_sortPersons_withId(mocker):
def mockPerson(id):
mock = mocker.patch('solo_turnier.worker.ResultPerson')
mock = mocker.patch("solo_turnier.worker.ResultPerson")
mock.id = id
return mock
persons = [mockPerson(2), mockPerson(1), mockPerson(5), mockPerson(3)]
dataWorker = worker.DataWorker()
sorted, showIds = dataWorker.sortPersonsInGroup(persons)
assert sorted == [persons[1], persons[0], persons[3], persons[2]]
assert showIds == True
def test_sortPersons_withoutId(mocker):
def mockPerson(name):
mock = mocker.patch('solo_turnier.worker.ResultPerson')
mock = mocker.patch("solo_turnier.worker.ResultPerson")
mock.id = 3
mock.name = name
mock.club = 'TSC Entenhausen'
mock.club = "TSC Entenhausen"
return mock
persons = [mockPerson('Max'), mockPerson('Isabel'), mockPerson('Reimund'), mockPerson('Anna')]
persons = [
mockPerson("Max"),
mockPerson("Isabel"),
mockPerson("Reimund"),
mockPerson("Anna"),
]
persons[2].id = None
dataWorker = worker.DataWorker()
sorted, showIds = dataWorker.sortPersonsInGroup(persons)
assert sorted == [persons[3], persons[1], persons[0], persons[2]]
assert showIds == False
def test_mapPersonResultsToDanceList(mocker):
def mockResult(dance):
mock = mocker.patch('solo_turnier.worker.CompetitionResult')
mock = mocker.patch("solo_turnier.worker.CompetitionResult")
mock.dance = dance
return mock
dances = ['Cha Cha', 'Rumba', 'Langs. Walzer', 'Quickstep']
results = [mockResult('Rumba'), mockResult('Quickstep'), mockResult('Cha Cha')]
dances = ["Cha Cha", "Rumba", "Langs. Walzer", "Quickstep"]
results = [mockResult("Rumba"), mockResult("Quickstep"), mockResult("Cha Cha")]
dataWorker = worker.DataWorker()
mappedResults = dataWorker.mapPersonResultsToDanceList(results, dances)
assert mappedResults == [results[2], results[0], None, results[1]]

View File

@ -1,12 +1,25 @@
from . import group
from . import competition_class
class CSVResultRow:
def __init__(self, firstName, lastName, club, id, group, class_, dance, place, placeTo, competitionGroup, competitionClass):
def __init__(
self,
firstName,
lastName,
club,
id,
group,
class_,
dance,
place,
placeTo,
competitionGroup,
competitionClass,
):
self.firstName = firstName
self.lastName = lastName
self.name = f'{firstName} {lastName}'
self.name = f"{firstName} {lastName}"
self.club = club
self.id = id
self.group = group
@ -16,9 +29,10 @@ class CSVResultRow:
self.placeTo = placeTo
self.competitionGroup = competitionGroup
self.competitionClass = competitionClass
def __repr__(self):
return f'{self.name} ({self.id}, {self.club}) is in {self.group} {self.class_} and danced the {self.dance} in {self.competitionGroup} {self.competitionClass} getting place {self.place}-{self.placeTo}'
return f"{self.name} ({self.id}, {self.club}) is in {self.group} {self.class_} and danced the {self.dance} in {self.competitionGroup} {self.competitionClass} getting place {self.place}-{self.placeTo}"
class HtmlPreviewParticipant:
def __init__(self, name, id, group_):
@ -27,74 +41,94 @@ class HtmlPreviewParticipant:
groupParser = group.GroupParser()
self.group = groupParser.parseClass(group_)
self.finalist = None
def __eq__(self, o):
if type(o) != HtmlPreviewParticipant:
return False
return all(map(lambda x, y: x == y, (self.name, self.id, self.group), (o.name, o.id, o.group)))
return all(
map(
lambda x, y: x == y,
(self.name, self.id, self.group),
(o.name, o.id, o.group),
)
)
def __repr__(self):
return f'{self.id} ({self.name}, {self.group})'
return f"{self.id} ({self.name}, {self.group})"
def __hash__(self):
return hash((self.id, self.name, self.group))
def __gt__(self, other):
return self.id >= other.id
class HtmlParticipant:
def __init__(self, name, id):
self.name = name
self.id = id
self.finalist = None
def __eq__(self, o):
if type(o) != HtmlPreviewParticipant:
return False
return all(map(lambda x, y: x == y, (self.name, self.id, self.group), (o.name, o.id, o.group)))
return all(
map(
lambda x, y: x == y,
(self.name, self.id, self.group),
(o.name, o.id, o.group),
)
)
def __repr__(self):
return f'{self.id}: {self.name}'
return f"{self.id}: {self.name}"
def __hash__(self):
return hash((self.id, self.name))
def __gt__(self, other):
return self.id >= other.id
# class PreviewParticipationData:
# def __init__(self, dance: str, class_: competition_class.CompetitionClass):
# self.class_ = class_
# self.dance = dance
class HtmlPreviewImport:
def __init__(
self,
participants: dict[int, list[HtmlPreviewParticipant]],
results: dict[HtmlPreviewParticipant, dict[str, competition_class.CompetitionClass]]
results: dict[
HtmlPreviewParticipant, dict[str, competition_class.CompetitionClass]
],
):
self.participants = participants
self.results = results
def __repr__(self):
return (str(self.participants), str(self.results))
class HtmlResultImport:
def __init__(self, results: dict[HtmlParticipant, str]):
self.results = results
def __repr__(self):
return str(self.results)
class HtmlResultTotalTable:
def __init__(self, participants):
self.participants = participants
def __repr__(self):
return str(self.participants)
class HtmlCompetitionResultRow:
def __init__(self, name, id, dance, group, class_, place, placeTo, finalist):
self.dance = dance
@ -105,70 +139,90 @@ class HtmlCompetitionResultRow:
self.id = int(id)
self.name = name
self.finalist = finalist
def __repr__(self):
if self.place == self.placeTo:
result = f'{self.place}.'
result = f"{self.place}."
else:
result = f'{self.place}.-{self.placeTo}.'
result = f"{self.place}.-{self.placeTo}."
if self.finalist == True:
finalist = '[F]'
finalist = "[F]"
else:
finalist = ''
return f'Result[{self.id}]({self.group} {self.class_} {self.dance} as {result}{finalist})'
finalist = ""
return f"Result[{self.id}]({self.group} {self.class_} {self.dance} as {result}{finalist})"
def __eq__(self, o):
if not isinstance(o, CompetitionResult):
return False
return (
self.dance == o.dance and
self.competitionClass == o.competitionClass and
self.competitionGroup == o.competitionGroup and
self.place == o.place and self.placeTo == o.placeTo and
self.id == o.id
self.dance == o.dance
and self.competitionClass == o.competitionClass
and self.competitionGroup == o.competitionGroup
and self.place == o.place
and self.placeTo == o.placeTo
and self.id == o.id
)
class HtmlSingleCompetitionResult:
def __init__(self, name, place, placeTo, finalist):
self.name = name
self.place = place
self.placeTo = placeTo
self.finalist = finalist
def __repr__(self):
if self.placeTo is None:
place = self.place
else:
place = f'{self.place}-{self.placeTo}'
place = f"{self.place}-{self.placeTo}"
if self.finalist:
return f'Res({self.name} [F], placed {place})'
return f"Res({self.name} [F], placed {place})"
else:
return f'Res({self.name}, placed {place})'
def __gt__(self,other):
return f"Res({self.name}, placed {place})"
def __gt__(self, other):
return self.id > other.id
def __eq__(self, other):
return self.id == other.id
def __hash__(self):
return hash(self.id)
class HtmlCompetitionTotalResults:
def __init__(self):
self.results = {}
self.tabges = {}
def __getTuple(self, group: group.Group_t, class_: competition_class.Class_t, dance: str, id: int):
def __getTuple(
self,
group: group.Group_t,
class_: competition_class.Class_t,
dance: str,
id: int,
):
return (group, class_, dance, id)
def get(self, group: group.Group_t, class_: competition_class.Class_t, dance: str, id: int) -> list[HtmlSingleCompetitionResult]:
def get(
self,
group: group.Group_t,
class_: competition_class.Class_t,
dance: str,
id: int,
) -> list[HtmlSingleCompetitionResult]:
return self.results[self.__getTuple(group, class_, dance, id)]
def getById(self, id: int) -> dict[tuple[str, group.Group_t, competition_class.Class_t], HtmlSingleCompetitionResult]:
def getById(
self, id: int
) -> dict[
tuple[str, group.Group_t, competition_class.Class_t],
HtmlSingleCompetitionResult,
]:
ret = {}
for k in self.results:
@ -178,15 +232,16 @@ class HtmlCompetitionTotalResults:
# Dance, Group, Class
key = (k[2], k[0], k[1])
ret[key] = self.results[k]
return ret
def add(self, group, class_, dance, id, result: HtmlSingleCompetitionResult):
tup = self.__getTuple(group, class_, dance, id)
l = self.results.get(tup, [])
l.append(result)
self.results[tup] = l
class SingleParticipantResult:
def __init__(
self,
@ -195,7 +250,7 @@ class SingleParticipantResult:
dance: str,
finalist: bool,
place: int,
placeTo: int|None
placeTo: int | None,
):
self.competitionClass = competitionClass
self.nativeClass = nativeClass
@ -209,67 +264,83 @@ class SingleParticipantResult:
self.placeNative = None
self.placeNativeTo = None
def __repr__(self):
asFinalist = ' as finalist' if self.finalist else ''
asFinalist = " as finalist" if self.finalist else ""
if self.placeTo is None:
return f'SR[{self.place} in {self.dance} {self.competitionClass} ({self.placeNative}-{self.placeNativeTo}, {self.nativeClass}){asFinalist}]'
return f"SR[{self.place} in {self.dance} {self.competitionClass} ({self.placeNative}-{self.placeNativeTo}, {self.nativeClass}){asFinalist}]"
return f"SR[{self.place}-{self.placeTo} in {self.dance} {self.competitionClass} ({self.placeNative}-{self.placeNativeTo}, {self.nativeClass}){asFinalist}]"
return f'SR[{self.place}-{self.placeTo} in {self.dance} {self.competitionClass} ({self.placeNative}-{self.placeNativeTo}, {self.nativeClass}){asFinalist}]'
def getPlace(self):
if self.placeTo is None:
return f'{self.place}.'
return f"{self.place}."
else:
return f'{self.place}.-{self.placeTo}.'
return f"{self.place}.-{self.placeTo}."
def getNativePlace(self):
if self.placeNativeTo is None:
return f'{self.placeNative}.'
return f"{self.placeNative}."
else:
return f'{self.placeNative}.-{self.placeNativeTo}.'
return f"{self.placeNative}.-{self.placeNativeTo}."
class TotalGroupResult:
def __init__(self, dances: list[str], results: dict[HtmlPreviewParticipant, list[SingleParticipantResult]]):
self.dances = dances
self.results = results
def __repr__(self):
return f'TotalGroupResult({self.dances}, {self.results})'
class State4:
def __init__(
self,
resultPerGroup: dict[group.Group, TotalGroupResult]
dances: list[str],
results: dict[HtmlPreviewParticipant, list[SingleParticipantResult]],
):
self.dances = dances
self.results = results
def __repr__(self):
return f"TotalGroupResult({self.dances}, {self.results})"
class State4:
def __init__(self, resultPerGroup: dict[group.Group, TotalGroupResult]):
parser = group.GroupParser()
self.groups = parser.getGroupsAsSortedList(resultPerGroup.keys())
self.results = resultPerGroup
class State3:
def __init__(
self,
previewImport: HtmlPreviewImport,
htmlResults: HtmlCompetitionTotalResults
self, previewImport: HtmlPreviewImport, htmlResults: HtmlCompetitionTotalResults
):
self.previewImport = previewImport
self.htmlResults = htmlResults
class Participant:
def __init__(self, firstName: str, lastName: str, club: str, group: group.Group, class_: competition_class.CompetitionClass):
def __init__(
self,
firstName: str,
lastName: str,
club: str,
group: group.Group,
class_: competition_class.CompetitionClass,
):
self.firstName = firstName
self.lastName = lastName
self.club = club
self.group = group
self.class_ = class_
class ParticipantResult:
def __init__(
self, id: int, finalist: bool, cancelled: bool,
self,
id: int,
finalist: bool,
cancelled: bool,
group: group.Group_t,
class_: competition_class.Class_t,
dance: str,
place, placeTo
place,
placeTo,
):
self.id = id
self.finalist = finalist
@ -280,10 +351,12 @@ class ParticipantResult:
self.place = place
self.placeTo = placeTo
class Stage2:
def __init__(self, results: dict[Participant, list[ParticipantResult]]):
self.results = results
class TableCompetitionEntry:
def __init__(
self,
@ -293,7 +366,7 @@ class TableCompetitionEntry:
place: int = -1,
placeTo: int = -1,
group: group.Group_t = None,
id: int = None
id: int = None,
):
self.finalist = finalist
self.cancelled = cancelled
@ -301,61 +374,60 @@ class TableCompetitionEntry:
self.class_ = class_
self.place = place
self.placeTo = placeTo
def __repr__(self):
def paramMerging(l):
return ', '.join(filter(lambda x: x is not None, l))
return ", ".join(filter(lambda x: x is not None, l))
if self.cancelled:
params = paramMerging([self.group, self.class_, self.id])
if len(params) > 0:
return f'- ({params})'
return f"- ({params})"
else:
return '-'
return "-"
elif not self.finalist:
params = paramMerging([self.group, self.class_, self.id])
if len(params) > 0:
return f'x ({params})'
return f"x ({params})"
else:
return 'x'
return "x"
else:
if self.place == self.placeTo:
place = f'{self.place}.'
place = f"{self.place}."
else:
place = f'{self.place}.-{self.placeTo}.'
place = f"{self.place}.-{self.placeTo}."
params = paramMerging([self.group, self.class_, self.id])
return f'{place} ({params})'
return f"{place} ({params})"
class TableEntry:
def __init__(self, competitions: list[TableCompetitionEntry]):
self.competitions = competitions
def __repr__(self):
return ', '.join(self.competitions)
return ", ".join(self.competitions)
class TableRow:
def __init__(
self,
participant: Participant,
id: int,
entries: list[TableEntry]
):
def __init__(self, participant: Participant, id: int, entries: list[TableEntry]):
self.participant = participant
self.id = id
self.entries = entries
def getRowList(self):
if self.id is not None:
first = f'{self.id}. {self.participant.firstName} {self.participant.lastName} ({self.participant.club})'
first = f"{self.id}. {self.participant.firstName} {self.participant.lastName} ({self.participant.club})"
else:
first = f'{self.participant.firstName} {self.participant.lastName} ({self.participant.club})'
first = f"{self.participant.firstName} {self.participant.lastName} ({self.participant.club})"
return [first] + map(str, self.entries)
class OutputTable:
def __init__(self, dances: list[str], rows: list[TableRow]):
self.dances = dances
self.rows = rows
class Stage1:
def __init__(self, tables: dict[group.Group, OutputTable]):
self.tables = tables

View File

@ -10,15 +10,16 @@ from .types import HtmlCompetitionResultRow as CompetitionResult
from . import types
from . import competition_class
class HtmlPerson:
def __init__(self, name, id, group):
self.name = name
self.id = id
self.group = group
def __repr__(self):
return f'{self.name} ({self.id}, {self.group})'
return f"{self.name} ({self.id}, {self.group})"
def __eq__(self, o):
if not isinstance(o, HtmlPerson):
return False
@ -27,40 +28,39 @@ class HtmlPerson:
def __hash__(self):
return str(self).__hash__()
class ResultPerson:
def __init__(self, firstName, lastName, club, id = None, group = None):
def __init__(self, firstName, lastName, club, id=None, group=None):
self.firstName = firstName
self.lastName = lastName
self.name = f'{firstName} {lastName}'
self.name = f"{firstName} {lastName}"
self.club = club
self.id = id
self.group = group
@staticmethod
def extractFromResultRow(row: ResultRow):
return ResultPerson(
firstName=row.firstName,
lastName=row.lastName,
club=row.club
firstName=row.firstName, lastName=row.lastName, club=row.club
)
def __eq__(self, o):
if not isinstance(o, ResultPerson):
return False
return (
self.firstName == o.firstName and
self.lastName == o.lastName and
self.club == o.club and
self.id == o.id
self.firstName == o.firstName
and self.lastName == o.lastName
and self.club == o.club
and self.id == o.id
)
def __repr__(self):
if self.id is None:
return f'{self.name} ({self.club})'
return f"{self.name} ({self.club})"
else:
return f'{self.name} ({self.club}) [{self.id}]'
return f"{self.name} ({self.club}) [{self.id}]"
def __hash__(self):
text = str(self)
return text.__hash__()
@ -69,83 +69,99 @@ class ResultPerson:
class ImportNotParsableException(Exception):
pass
ParserList_t = dict[str, html_parser.HtmlParser]
class PreviewWorker:
def __init__(self):
self.l = logging.getLogger('solo_turnier.worker.PreviewWorker')
self.l = logging.getLogger("solo_turnier.worker.PreviewWorker")
self.participants = {}
self.previewResults = {}
def filterFilesPreview(self, files: list[str]) -> ParserList_t:
self.l.debug('Filtering the list of parsers by removing all non preview entries.')
self.l.debug(
"Filtering the list of parsers by removing all non preview entries."
)
ret = {}
for file in files:
with open(file, 'r') as fp:
with open(file, "r") as fp:
text = fp.read()
parser = html_parser.HtmlParser(text, file)
try:
data = parser.guessDataFromHtmlTitle()
except:
self.l.error(f'Unable to parse html file in {file}. Please check manually.')
self.l.error(
f"Unable to parse html file in {file}. Please check manually."
)
continue
if data['class_'] == 'Sichtung':
if data["class_"] == "Sichtung":
self.l.debug(f"Found candidate in {file}. Adding to the list.")
ret[file] = parser
else:
self.l.debug(f'Rejecting file {file} as the name {data["class_"]} did not match.')
self.l.debug(
f'Rejecting file {file} as the name {data["class_"]} did not match.'
)
return ret
def __extractPersonsFromSinglePreview(self, parser: html_parser.HtmlParser):
imported = parser.parsePreparationRound()
parser.cleanPreparationRoundImport(imported)
data = imported['data']
data = imported["data"]
headerData = parser.guessDataFromHtmlTitle()
dance = headerData['dance']
dance = headerData["dance"]
classParser = solo_turnier.competition_class.CompetitionClassParser()
def getRowIndexOfClass():
return data['titles'].index('Platz von\nPlatz bis')
self.l.log(5, data)
if data['titles'][0] != 'Wertungsrichter':
self.l.fatal('Cannot parse the parsed content of the preview file.')
raise ImportNotParsableException('Incompatible export file')
return data["titles"].index("Platz von\nPlatz bis")
if data['titles'][-1] == 'Startgruppe':
self.l.debug('Combined competition found. Extracting group from table required.')
self.l.log(5, data)
if data["titles"][0] != "Wertungsrichter":
self.l.fatal("Cannot parse the parsed content of the preview file.")
raise ImportNotParsableException("Incompatible export file")
if data["titles"][-1] == "Startgruppe":
self.l.debug(
"Combined competition found. Extracting group from table required."
)
extractGroup = True
else:
self.l.debug('Using group from the title.')
group = parser.guessDataFromHtmlTitle(imported['title'])['group']
self.l.debug("Using group from the title.")
group = parser.guessDataFromHtmlTitle(imported["title"])["group"]
extractGroup = False
classRowIndex = getRowIndexOfClass()
for index, e in enumerate(data['table'][0]):
if e['text'] == '':
for index, e in enumerate(data["table"][0]):
if e["text"] == "":
# Skip empty columns
continue
# Extract data from column
name = e['meta']
id = int(e['text'])
if extractGroup:
group = data['table'][-1][index]['text']
# dance =
class_ = classParser.parseClass(data['table'][classRowIndex][index]['text'])
# Extract data from column
name = e["meta"]
id = int(e["text"])
if extractGroup:
group = data["table"][-1][index]["text"]
# dance =
class_ = classParser.parseClass(data["table"][classRowIndex][index]["text"])
participant = types.HtmlPreviewParticipant(name, id, group)
l = self.participants.get(id, [])
self.l.log(5, 'Checking for existence of %s in %s: %s', participant, l, participant in l)
self.l.log(
5,
"Checking for existence of %s in %s: %s",
participant,
l,
participant in l,
)
if participant not in l:
l.append(participant)
self.participants[id] = l
@ -162,118 +178,146 @@ class PreviewWorker:
try:
self.__extractPersonsFromSinglePreview(parser)
except:
self.l.error('Failed to parse preview round in file %s. Skipping this file\'s content.', parser.fileName)
self.l.error(
"Failed to parse preview round in file %s. Skipping this file's content.",
parser.fileName,
)
return types.HtmlPreviewImport(self.participants, self.previewResults)
class ResultExtractor:
def __init__(self):
self.l = logging.getLogger('solo_turnier.worker.ResultExtractor')
self.rePlaceSingle = re.compile(' *([0-9]+) *')
self.rePlaceDouble = re.compile(' *([0-9]+) *- *([0-9]+) *')
def getAllParsers(self, files: list[tuple[str,str]]) -> ParserList_t:
self.l = logging.getLogger("solo_turnier.worker.ResultExtractor")
self.rePlaceSingle = re.compile(" *([0-9]+) *")
self.rePlaceDouble = re.compile(" *([0-9]+) *- *([0-9]+) *")
def getAllParsers(self, files: list[tuple[str, str]]) -> ParserList_t:
ret = {}
classParser = competition_class.CompetitionClassParser()
for filePair in files:
with open(filePair[0], 'r') as fp:
with open(filePair[0], "r") as fp:
text = fp.read()
parser = html_parser.HtmlParser(text, filePair[0])
if filePair[1] is None:
parserTab = None
else:
with open(filePair[1], 'r') as fp:
with open(filePair[1], "r") as fp:
textTab = fp.read()
parserTab = html_parser.HtmlParser(textTab, filePair[1])
try:
data = parser.guessDataFromHtmlTitle()
except:
self.l.error('Cannot parse HTML file %s to check if it is a valid result. Check manually.', filePair[0])
self.l.error(
"Cannot parse HTML file %s to check if it is a valid result. Check manually.",
filePair[0],
)
continue
try:
guessedClass = classParser.parseClass(data['class_'])
guessedClass = classParser.parseClass(data["class_"])
except:
self.l.error('Issue parsing class of file %s. Check manually.', filePair[0])
self.l.error(
"Issue parsing class of file %s. Check manually.", filePair[0]
)
continue
self.l.debug('Fetched result data: %s, guessed class %s', data, guessedClass)
self.l.debug(
"Fetched result data: %s, guessed class %s", data, guessedClass
)
ret[filePair] = (parser, parserTab)
return ret
def _extractPlace(self, placeStr: str):
s = placeStr.replace('.', '')
s = placeStr.replace(".", "")
matches = self.rePlaceSingle.fullmatch(s)
if matches is not None:
return (int(matches.group(1)), None)
matches = self.rePlaceDouble.fullmatch(s)
if matches is not None:
return (int(matches.group(1)), int(matches.group(2)))
self.l.error('Could not parse place string "%s"', placeStr)
raise Exception('Place cannot be parsed')
raise Exception("Place cannot be parsed")
def _analyzeSingleParser(self, parser: html_parser.HtmlParser, results: types.HtmlCompetitionTotalResults):
def _analyzeSingleParser(
self, parser: html_parser.HtmlParser, results: types.HtmlCompetitionTotalResults
):
data = parser.guessDataFromHtmlTitle()
competitionClass = data['class_']
competitionGroup = data['group']
dance = data['dance']
competitionClass = data["class_"]
competitionGroup = data["group"]
dance = data["dance"]
result = parser.parseResult()
self.l.log(5, 'Raw data extracted: %s', result)
self.l.log(5, "Raw data extracted: %s", result)
for person in result.results.keys():
placeStr = result.results[person]
place, placeTo = self._extractPlace(placeStr)
competitionResult = types.HtmlSingleCompetitionResult(person.name, place, placeTo, person.finalist)
results.add(competitionGroup, competitionClass, dance, person.id, competitionResult)
#
competitionResult = types.HtmlSingleCompetitionResult(
person.name, place, placeTo, person.finalist
)
results.add(
competitionGroup, competitionClass, dance, person.id, competitionResult
)
#
def _analyzeIndividualResults(self, parser: html_parser.HtmlParser, results: types.HtmlCompetitionTotalResults):
def _analyzeIndividualResults(
self, parser: html_parser.HtmlParser, results: types.HtmlCompetitionTotalResults
):
data = parser.guessDataFromHtmlTitle()
competitionClass = data['class_']
competitionGroup = data['group']
dance = data['dance']
competitionClass = data["class_"]
competitionGroup = data["group"]
dance = data["dance"]
result = parser.parseIndividualResult(competitionGroup, competitionClass, dance)
self.l.log(5, 'Found individual results: %s', result.participants)
self.l.log(5, "Found individual results: %s", result.participants)
results.tabges.update(result.participants)
def extractAllData(self, parsers: ParserList_t) -> types.HtmlCompetitionTotalResults:
def extractAllData(
self, parsers: ParserList_t
) -> types.HtmlCompetitionTotalResults:
ret = types.HtmlCompetitionTotalResults()
for fileNameTuple in parsers:
fileName = fileNameTuple[0]
self.l.debug('Extracting data from file %s', fileName)
self.l.debug("Extracting data from file %s", fileName)
self._analyzeSingleParser(parsers[fileNameTuple][0], ret)
if parsers[fileNameTuple][1] is None:
self.l.info('Skipping extraction of individual result as class is not yet finished.')
self.l.info(
"Skipping extraction of individual result as class is not yet finished."
)
else:
self.l.debug('Fetching individual result of combined competitions in %s', fileName)
self.l.debug(
"Fetching individual result of combined competitions in %s",
fileName,
)
self._analyzeIndividualResults(parsers[fileNameTuple][1], ret)
return ret
class DataWorker:
def __init__(self):
self.l = logging.getLogger('solo_turnier.worker.DataWorker')
def combineRowsByPerson(self, rows: list[ResultRow]) -> dict[ResultPerson, list[CompetitionResult]]:
self.l = logging.getLogger("solo_turnier.worker.DataWorker")
def combineRowsByPerson(
self, rows: list[ResultRow]
) -> dict[ResultPerson, list[CompetitionResult]]:
ret = {}
for row in rows:
result = CompetitionResult.extractFromResultRow(row)
if result.place == '-' or result.placeTo == '-':
if result.place == "-" or result.placeTo == "-":
continue
person = ResultPerson.extractFromResultRow(row)
if person not in ret:
ret[person] = []
@ -297,69 +341,94 @@ class DataWorker:
The second one is True if there was the need to override a group but it was possible to extract from other data
The second one can be seen as a warning
"""
def consolidateGroups(self, data:dict[ResultPerson, list[CompetitionResult]]) -> tuple[bool, bool]:
def consolidateGroups(
self, data: dict[ResultPerson, list[CompetitionResult]]
) -> tuple[bool, bool]:
ambiguous = False
warnChange = False
unambiguousGroups = set(['Kin.', 'Jun.', 'Jug.'])
combinations = set(['Kin./Jun.', 'Jun./Jug.'])
unambiguousGroups = set(["Kin.", "Jun.", "Jug."])
combinations = set(["Kin./Jun.", "Jun./Jug."])
for person in data:
groupsRaw = set([c.group for c in data[person]])
unknown = groupsRaw.difference(unambiguousGroups).difference(combinations)
if len(unknown) > 0:
raise Exception(f'There were unknown groups found for {person}: {unknown}')
raise Exception(
f"There were unknown groups found for {person}: {unknown}"
)
numUnambiguousGroups = len(groupsRaw.intersection(unambiguousGroups))
if numUnambiguousGroups == 0:
if len(groupsRaw) == 2:
warnChange = True
person.group = 'Jun.'
person.group = "Jun."
else:
ambiguous = True
if len(groupsRaw) == 1:
person.group = list(groupsRaw)[0]
elif numUnambiguousGroups == 1:
if len(groupsRaw.intersection(combinations)) > 0:
warnChange = True
person.group = list(groupsRaw.intersection(unambiguousGroups))[0]
else:
raise Exception(f'{person} cannot have different groups.')
raise Exception(f"{person} cannot have different groups.")
return (not ambiguous, warnChange)
def _createHtmlLUT(self, htmlImports: list[html_parser.HtmlImport]):
ret = {}
parser = html_parser.HtmlParser('')
parser = html_parser.HtmlParser("")
for imp in htmlImports:
parsed = parser.guessDataFromHtmlTitle(imp.title)
key = (parsed['group'], parsed['class_'], parsed['dance'])
key = (parsed["group"], parsed["class_"], parsed["dance"])
ret[key] = imp
self.l.debug('LUT[%s] = %s', key, imp)
self.l.debug('LUT completed')
self.l.debug("LUT[%s] = %s", key, imp)
self.l.debug("LUT completed")
return ret
def mergeHtmlData(self, data:dict[ResultPerson, list[CompetitionResult]], htmlImports: list[html_parser.HtmlImport]):
def mergeHtmlData(
self,
data: dict[ResultPerson, list[CompetitionResult]],
htmlImports: list[html_parser.HtmlImport],
):
lut = self._createHtmlLUT(htmlImports)
for person in data:
for competition in data[person]:
key = (competition.competitionGroup, competition.competitionClass, competition.dance)
key = (
competition.competitionGroup,
competition.competitionClass,
competition.dance,
)
htmlImport = lut[key]
participant = htmlImport.participants[str(competition.id)]
if participant.name != person.name:
self.l.error(f'Names for {person} and participant in HTML import ({participant}) do not match. Please check carefully.')
self.l.error(
f"Names for {person} and participant in HTML import ({participant}) do not match. Please check carefully."
)
competition.finalist = participant.finalist
def getAllDancesInCompetitions(self, data:dict[ResultPerson, list[CompetitionResult]]) -> list[str]:
def getAllDancesInCompetitions(
self, data: dict[ResultPerson, list[CompetitionResult]]
) -> list[str]:
allDances = [
'Samba', 'Cha Cha', 'Rumba', 'Paso Doble', 'Jive',
'Langs. Walzer', 'Tango', 'Wiener Walzer', 'Slowfox', 'Quickstep'
"Samba",
"Cha Cha",
"Rumba",
"Paso Doble",
"Jive",
"Langs. Walzer",
"Tango",
"Wiener Walzer",
"Slowfox",
"Quickstep",
]
dancesPresent = {d: False for d in allDances}
@ -369,21 +438,24 @@ class DataWorker:
return [d for d in allDances if dancesPresent[d]]
def collectPersonsInGroups(self, data:dict[ResultPerson, list[CompetitionResult]]) -> list[tuple[str, list[ResultPerson]]]:
def collectPersonsInGroups(
self, data: dict[ResultPerson, list[CompetitionResult]]
) -> list[tuple[str, list[ResultPerson]]]:
groups = {
'Kin.': [p for p in data.keys() if p.group == 'Kin.'],
'Jun.': [p for p in data.keys() if p.group == 'Jun.'],
'Jug.': [p for p in data.keys() if p.group == 'Jug.'],
"Kin.": [p for p in data.keys() if p.group == "Kin."],
"Jun.": [p for p in data.keys() if p.group == "Jun."],
"Jug.": [p for p in data.keys() if p.group == "Jug."],
}
found = groups['Kin.'] + groups['Jun.'] + groups['Jug.']
groups['Sonst'] = [p for p in data.keys() if p not in found]
found = groups["Kin."] + groups["Jun."] + groups["Jug."]
groups["Sonst"] = [p for p in data.keys() if p not in found]
return groups
def sortPersonsInGroup(self, persons: list[ResultPerson]) -> list[ResultPerson]:
ids = [p.id for p in persons]
def decorateByName(p: ResultPerson):
return (f'{p.name} ({p.club})', p)
return (f"{p.name} ({p.club})", p)
def decorateById(p: ResultPerson):
return (p.id, p)
@ -394,96 +466,106 @@ class DataWorker:
else:
decorated = [decorateById(p) for p in persons]
showIds = True
decorated.sort()
return ([d[1] for d in decorated], showIds)
def mapPersonResultsToDanceList(self, results: list[CompetitionResult], dances: list[str]) -> list[CompetitionResult|None]:
def mapPersonResultsToDanceList(
self, results: list[CompetitionResult], dances: list[str]
) -> list[CompetitionResult | None]:
ret = []
for dance in dances:
competitions = [c for c in results if c.dance == dance]
if len(competitions) == 0:
ret.append(None)
elif len(competitions) > 1:
raise Exception(f'Multiple competitions with the same dance "{dance}" found.')
raise Exception(
f'Multiple competitions with the same dance "{dance}" found.'
)
else:
ret.append(competitions[0])
return ret
class Worker:
def __init__(self):
self.l = logging.getLogger('solo_turnier.worker.Worker')
self._allDances = (
['Samba', 'Cha Cha', 'Rumba', 'Paso Doble', 'Jive'] +
['Langs. Walzer', 'Tango', 'Wiener Walzer', 'Slowfox', 'Quickstep']
)
def collectAllData(
self,
htmlCandidatesPreview: list[str],
htmlResultsFileNames: list[str]
) -> types.State3:
previewWorker = PreviewWorker()
self.l.info('Filtering for pure preview rounds.')
parsers = previewWorker.filterFilesPreview(htmlCandidatesPreview)
self.l.debug('Remaining files: %s', list(parsers.keys()))
self.l = logging.getLogger("solo_turnier.worker.Worker")
self._allDances = ["Samba", "Cha Cha", "Rumba", "Paso Doble", "Jive"] + [
"Langs. Walzer",
"Tango",
"Wiener Walzer",
"Slowfox",
"Quickstep",
]
self.l.info('Extracting person data from the preview rounds.')
def collectAllData(
self, htmlCandidatesPreview: list[str], htmlResultsFileNames: list[str]
) -> types.State3:
previewWorker = PreviewWorker()
self.l.info("Filtering for pure preview rounds.")
parsers = previewWorker.filterFilesPreview(htmlCandidatesPreview)
self.l.debug("Remaining files: %s", list(parsers.keys()))
self.l.info("Extracting person data from the preview rounds.")
previewImport = previewWorker.importAllData(parsers)
self.l.debug('Total preview imported participants: %s', pformat(previewImport.participants))
self.l.log(5, 'Total preview results: %s', pformat(previewImport.results))
self.l.debug(
"Total preview imported participants: %s",
pformat(previewImport.participants),
)
self.l.log(5, "Total preview results: %s", pformat(previewImport.results))
resultExtractor = ResultExtractor()
resultParsers = resultExtractor.getAllParsers(htmlResultsFileNames)
htmlResults = resultExtractor.extractAllData(resultParsers)
self.l.info('Overall result data extracted: %s', pformat(htmlResults.results))
self.l.info("Overall result data extracted: %s", pformat(htmlResults.results))
return types.State3(previewImport, htmlResults)
def combineData(self, importedData: types.State3):
self.l.info('Starting to build data sets.')
self.l.info("Starting to build data sets.")
groups = self._extractGroups(importedData)
self.l.debug('Found groups in the dataset: %s', groups)
self.l.debug("Found groups in the dataset: %s", groups)
totalResult = {}
for group in groups:
self.l.debug('Collecting data for total result of group %s', group)
self.l.debug("Collecting data for total result of group %s", group)
dances = self._extractDancesPerGroup(importedData, group)
self.l.log(5, 'Found dances in group %s: %s', group, dances)
self.l.log(5, "Found dances in group %s: %s", group, dances)
participants = self._extractParticipantsPerGroup(importedData, group)
self.l.log(5, 'Related participants %s', participants)
self.l.log(5, "Related participants %s", participants)
results = {}
for participant in participants:
self.l.log(5, 'Collecting data for %s', participant)
self.l.log(5, "Collecting data for %s", participant)
resultsOfParticipant = self._getResultOfSingleParticipant(
participant, group, importedData.previewImport,
importedData.htmlResults, dances
)
self.l.log(5, 'Obtained result %s', resultsOfParticipant)
participant,
group,
importedData.previewImport,
importedData.htmlResults,
dances,
)
self.l.log(5, "Obtained result %s", resultsOfParticipant)
results[participant] = resultsOfParticipant
self.l.log(5, 'Result before native fixing: %s', pformat(results))
self.l.log(5, "Result before native fixing: %s", pformat(results))
# self._fixNativePlaces(dances, results)
self._fixNativeDataFromTable(dances, results, importedData.htmlResults)
self.l.log(5, 'Result after native fixing: %s', pformat(results))
self.l.log(5, "Result after native fixing: %s", pformat(results))
# self.l.log(5,'Fixed data %s', results)
totalResult[group] = types.TotalGroupResult(dances, results)
self.l.log(5, 'Total result of all groups: %s', pformat(totalResult))
self.l.log(5, "Total result of all groups: %s", pformat(totalResult))
ret = types.State4(totalResult)
return ret
def _extractGroups(self, data: types.State3):
groupParser = solo_turnier.group.GroupParser()
@ -497,12 +579,14 @@ class Worker:
# groupSet.add(gr)
groupSet.update(gr.getContainedGroups())
# self.l.log(5, 'Group type %s', type(gr))
self.l.log(5, 'Set of active groups: %s', groupSet)
self.l.log(5, "Set of active groups: %s", groupSet)
groups = groupParser.getGroupsAsSortedList(groupSet)
return groups
def _extractDancesPerGroup(self, data: types.State3, group: solo_turnier.group.Group):
def _extractDancesPerGroup(
self, data: types.State3, group: solo_turnier.group.Group
):
groupParser = solo_turnier.group.GroupParser()
dances = set()
@ -513,24 +597,27 @@ class Worker:
if group not in currentGroup.getContainedGroups():
continue
foundDances.add(tup[2])
dances.update(foundDances.intersection(self._allDances))
additionalDances.update(foundDances.difference(self._allDances))
if len(additionalDances) > 0:
self.l.error('There were dances found, that are not registered. A bug? The dances were: %s', additionalDances)
self.l.error(
"There were dances found, that are not registered. A bug? The dances were: %s",
additionalDances,
)
dancesList = [x for x in self._allDances if x in dances]
additionalDancesList = list(additionalDances)
additionalDancesList.sort()
return dancesList + additionalDancesList
def _extractParticipantsPerGroup(
self,
importedData: types.State3,
# previewData: types.HtmlPreviewImport,
group: solo_turnier.group.Group
) -> list[types.HtmlPreviewParticipant]:
group: solo_turnier.group.Group,
) -> list[types.HtmlPreviewParticipant]:
groupParser = types.group.GroupParser()
ret = []
@ -546,14 +633,14 @@ class Worker:
fixture = importedData.htmlResults.tabges[tup]
if fixture[2] is not None and fixture[2] != group:
self.l.log(5, 'Skipping id %s in group %s as not part', tup[3], group)
self.l.log(5, "Skipping id %s in group %s as not part", tup[3], group)
continue
part = importedData.htmlResults.results[tup][0]
part.id = int(tup[3])
ret.append(part)
self.l.log(5, 'ret %s', ret)
self.l.log(5, "ret %s", ret)
# raise Exception('Test')
# for id in previewData.participants:
@ -562,30 +649,32 @@ class Worker:
# if participant.group == group:
# ret.append(participant)
return ret
def _getResultOfSingleParticipant(
self,
participant: types.HtmlParticipant,
nominalGroup: solo_turnier.group.Group,
previewResults: types.HtmlPreviewImport,
totalResults: types.HtmlCompetitionTotalResults,
allDances: list[str]
) -> list[types.SingleParticipantResult|None]:
allDances: list[str],
) -> list[types.SingleParticipantResult | None]:
rawResults = totalResults.getById(participant.id)
self.l.log(5, 'Found result data for id %i (raw): %s', participant.id, rawResults)
self.l.log(
5, "Found result data for id %i (raw): %s", participant.id, rawResults
)
results = [None for x in allDances]
for danceIdx, dance in enumerate(allDances):
# self.l.log(5, '%s %s', dance, danceIdx)
def getResult() -> types.SingleParticipantResult|None:
def getResult() -> types.SingleParticipantResult | None:
for key in rawResults:
if key[0] != dance:
continue
rawResult = rawResults[key]
if len(rawResult) != 1:
raise Exception('Multiple results found with same key')
raise Exception("Multiple results found with same key")
rawResult = rawResult[0]
nativeClass = key[2]
@ -594,33 +683,37 @@ class Worker:
# self.l.log(5, 'Result %s => %s', key, rawResult)
ret = types.SingleParticipantResult(
key[2], nativeClass, dance, rawResult.finalist,
rawResult.place, rawResult.placeTo
key[2],
nativeClass,
dance,
rawResult.finalist,
rawResult.place,
rawResult.placeTo,
)
return ret
return None
results[danceIdx] = getResult()
return results
def _fixNativeDataFromTable(
self,
dances: list[str],
data: dict[types.HtmlPreviewParticipant, list[types.SingleParticipantResult]],
importedData: types.HtmlCompetitionTotalResults
):
rePlace = re.compile('([0-9]+)(?:-([0-9]+))?')
importedData: types.HtmlCompetitionTotalResults,
):
rePlace = re.compile("([0-9]+)(?:-([0-9]+))?")
classParser = competition_class.CompetitionClassParser()
for participant in data.keys():
self.l.log(5, 'fixing participant %s', participant)
self.l.log(5, "fixing participant %s", participant)
results = data[participant]
for result in results:
if result is None:
continue
self.l.log(5, 'Looking at result set %s', result)
self.l.log(5, "Looking at result set %s", result)
def selectEntry(k):
return k[2] == result.dance and int(k[3]) == participant.id
@ -633,13 +726,19 @@ class Worker:
continue
raw = importedData.tabges[keys[selectedIndex]]
self.l.log(5,'Raw %s', raw)
self.l.log(5, "Raw %s", raw)
nativePlaceRaw = raw[0]
matcher = rePlace.fullmatch(nativePlaceRaw)
if matcher is None:
self.l.error('Cannot parse place string %s for participant %u (%s) in dance %s', nativePlaceRaw, participant.id, participant, result.dance)
self.l.error(
"Cannot parse place string %s for participant %u (%s) in dance %s",
nativePlaceRaw,
participant.id,
participant,
result.dance,
)
continue
self.l.log(5, 'Found strings by regex: %s', matcher.groups())
self.l.log(5, "Found strings by regex: %s", matcher.groups())
result.placeNative = matcher.group(1)
result.placeNativeTo = matcher.group(2)
@ -651,32 +750,36 @@ class Worker:
def _fixNativePlaces(
self,
dances: list[str],
data: dict[types.HtmlPreviewParticipant, list[types.SingleParticipantResult]]
):
data: dict[types.HtmlPreviewParticipant, list[types.SingleParticipantResult]],
):
classParser = solo_turnier.competition_class.CompetitionClassParser()
allClasses = classParser.getAllClasses()
allClasses.reverse()
for class_ in allClasses:
for danceIdx, dance in enumerate(dances):
self.l.log(5, 'Fixing native places for class %s in dance %s', class_, dance)
self.l.log(
5, "Fixing native places for class %s in dance %s", class_, dance
)
remainingParticipants = []
for participant in data.keys():
results = data[participant]
danceResult = results[danceIdx]
if danceResult is None:
continue
# self.l.log(5, 'Result of dance: %s', danceResult)
if classParser.isABetterThanB(danceResult.nativeClass, class_):
# self.l.log(5, 'Skipping %s as the native class is higher', participant)
continue
remainingParticipants.append((danceResult.place, participant.id, participant))
remainingParticipants.append(
(danceResult.place, participant.id, participant)
)
remainingParticipants.sort()
# self.l.log(5, 'Remaining participants %s', remainingParticipants)
@ -684,17 +787,20 @@ class Worker:
def getAllParticipantsWithSamePlace():
first = remainingParticipants.pop(0)
ret = [first]
while len(remainingParticipants) > 0 and remainingParticipants[0][0] == first[0]:
while (
len(remainingParticipants) > 0
and remainingParticipants[0][0] == first[0]
):
ret.append(remainingParticipants.pop(0))
return ret
def updateNativePlaces(samePlaced, placeStart):
nextPlace = placeStart + len(samePlaced)
if len(samePlaced) == 1:
placeTo = None
else:
placeTo = nextPlace - 1
for p in samePlaced:
data[p[2]][danceIdx].placeNative = placeStart
data[p[2]][danceIdx].placeNativeTo = placeTo
@ -706,33 +812,38 @@ class Worker:
while len(remainingParticipants) > 0:
samePlaced = getAllParticipantsWithSamePlace()
place = updateNativePlaces(samePlaced, place)
# self.l.log(5, '(Partially) fixed places: %s', (data))
def filterOutFinalists(self, data: types.State4, filterOut: bool):
for group in data.results:
self.l.debug('Cleaning up group %s', group.name)
self.l.debug("Cleaning up group %s", group.name)
participants = data.results[group].results.keys()
droppedParticipants = []
for participant in participants:
self.l.debug('Checking %s', participant)
self.l.debug("Checking %s", participant)
def isFinalistInDance(x: types.HtmlSingleCompetitionResult|None):
def isFinalistInDance(x: types.HtmlSingleCompetitionResult | None):
if x is None:
return False
return x.finalist
mapped = list(map(isFinalistInDance, data.results[group].results[participant]))
mapped = list(
map(isFinalistInDance, data.results[group].results[participant])
)
finalist = True in mapped
self.l.log(5,'Check for finalist (in dances %s): %s', mapped, finalist)
self.l.log(5, "Check for finalist (in dances %s): %s", mapped, finalist)
if finalist:
participant.finalist = True
else:
participant.finalist = False
self.l.info('Dropping %s from the output as no finalist', participant)
self.l.info(
"Dropping %s from the output as no finalist", participant
)
droppedParticipants.append(participant)
if filterOut:
for droppedParticipant in droppedParticipants:
data.results[group].results.pop(droppedParticipant)