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

View File

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

View File

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

View File

@ -3,19 +3,50 @@ import logging
import debugpy import debugpy
class Cli: class Cli:
def __init__(self, l: logging.Logger): def __init__(self, l: logging.Logger):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
# parser.add_argument('--gui', help='Show the GUI', action='store_true') # 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(
parser.add_argument('--port', help='The port to listen for incoming requests', default='8082') "--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(
parser.add_argument('-o', '--output', help='Set the output path of the script', nargs=1, default=[None]) "html",
parser.add_argument('--all-participants', '-a', action='store_true', help='Show all participants not only finalists') 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(
parser.add_argument('-d', '--debug', action='store_true', help='Activate debugging during startup') "-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() self.__args = parser.parse_args()
if self.__args.debug: if self.__args.debug:

View File

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

View File

@ -4,29 +4,29 @@ import logging
_l = logging.getLogger(__name__) _l = logging.getLogger(__name__)
def startFlask( def startFlask(
batchWorker: solo_turnier.batch.BatchWorker, batchWorker: solo_turnier.batch.BatchWorker,
debug: bool = False, debug: bool = False,
port: int = 8082, port: int = 8082,
showOnlyFinalists: bool = True, showOnlyFinalists: bool = True,
externalDebugger: bool = False, externalDebugger: bool = False,
): ):
app = flask.Flask(__name__) app = flask.Flask(__name__)
@app.route('/') @app.route("/")
def index(): def index():
combinedData = batchWorker.run(False) 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) 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.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 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,5 +1,6 @@
import re import re
class Group: class Group:
def __init__(self, text: str): def __init__(self, text: str):
self.name = text self.name = text
@ -10,70 +11,68 @@ class Group:
def getContainedGroups(self): def getContainedGroups(self):
return (self,) return (self,)
class CombinedGroup: class CombinedGroup:
def __init__(self, grpA: Group, grpB: Group): def __init__(self, grpA: Group, grpB: Group):
self.clsA = grpA self.clsA = grpA
self.clsB = grpB self.clsB = grpB
def __repr__(self): def __repr__(self):
return f'{self.clsA}/{self.clsB}' return f"{self.clsA}/{self.clsB}"
def getContainedGroups(self): def getContainedGroups(self):
return (self.clsA, self.clsB) return (self.clsA, self.clsB)
Group_t = Group | CombinedGroup Group_t = Group | CombinedGroup
class GroupParser: class GroupParser:
KIN = Group('Kin.') KIN = Group("Kin.")
JUN = Group('Jun.') JUN = Group("Jun.")
JUG = Group('Jug.') JUG = Group("Jug.")
HGR = Group('Hgr.') HGR = Group("Hgr.")
MAS1 = Group('Mas. I') MAS1 = Group("Mas. I")
MAS2 = Group('Mas. II') MAS2 = Group("Mas. II")
MAS3 = Group('Mas. III') MAS3 = Group("Mas. III")
MAS4 = Group('Mas. IV') MAS4 = Group("Mas. IV")
MAS5 = Group('Mas. V') MAS5 = Group("Mas. V")
def __init__(self): def __init__(self):
self.mapNames = { self.mapNames = {
'Kin': self.KIN, "Kin": self.KIN,
'Kin.': self.KIN, "Kin.": self.KIN,
'Kinder': self.KIN, "Kinder": self.KIN,
"Jun": self.JUN,
'Jun': self.JUN, "Jun.": self.JUN,
'Jun.': self.JUN, "Junioren": self.JUN,
'Junioren': self.JUN, "Jug": self.JUG,
"Jug.": self.JUG,
'Jug': self.JUG, "Jugend": self.JUG,
'Jug.': self.JUG, "Hgr": self.HGR,
'Jugend': self.JUG, "HGr": self.HGR,
"Hgr.": self.HGR,
'Hgr': self.HGR, "HGr.": self.HGR,
'HGr': self.HGR, "Hauptgruppe": self.HGR,
'Hgr.': self.HGR, "Mas. I": self.MAS1,
'HGr.': self.HGR, "Mas. II": self.MAS2,
'Hauptgruppe': self.HGR, "Mas. III": self.MAS3,
"Mas. IV": self.MAS4,
'Mas. I': self.MAS1, "Mas. V": self.MAS5,
'Mas. II': self.MAS2, "Mas I": self.MAS1,
'Mas. III': self.MAS3, "Mas II": self.MAS2,
'Mas. IV': self.MAS4, "Mas III": self.MAS3,
'Mas. V': self.MAS5, "Mas IV": self.MAS4,
'Mas I': self.MAS1, "Mas V": self.MAS5,
'Mas II': self.MAS2, "Masters I": self.MAS1,
'Mas III': self.MAS3, "Masters II": self.MAS2,
'Mas IV': self.MAS4, "Masters III": self.MAS3,
'Mas V': self.MAS5, "Masters IV": self.MAS4,
'Masters I': self.MAS1, "Masters V": self.MAS5,
'Masters II': self.MAS2,
'Masters III': self.MAS3,
'Masters IV': self.MAS4,
'Masters V': self.MAS5,
} }
def parseClass(self, cls: str) -> Group_t: 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: if match is not None:
grpA = self.mapNames[match.group(1)] grpA = self.mapNames[match.group(1)]
grpB = self.mapNames[match.group(2)] grpB = self.mapNames[match.group(2)]
@ -86,7 +85,7 @@ class GroupParser:
return isinstance(parsedClass, Group) return isinstance(parsedClass, Group)
def getGroups(self) -> list[Group]: def getGroups(self) -> list[Group]:
return[ return [
GroupParser.KIN, GroupParser.KIN,
GroupParser.JUN, GroupParser.JUN,
GroupParser.JUG, GroupParser.JUG,
@ -95,7 +94,7 @@ class GroupParser:
GroupParser.MAS2, GroupParser.MAS2,
GroupParser.MAS3, GroupParser.MAS3,
GroupParser.MAS4, GroupParser.MAS4,
GroupParser.MAS5 GroupParser.MAS5,
] ]
def getGroupsAsSortedList(self, groups) -> list[Group]: def getGroupsAsSortedList(self, groups) -> list[Group]:

View File

@ -1,9 +1,10 @@
import os import os
import logging import logging
class HtmlLocator: class HtmlLocator:
def __init__(self): 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): def __findRecursivelyCandidates(self, path: str, fileName: str):
ret = [] ret = []
@ -21,16 +22,16 @@ class HtmlLocator:
def __fingMatchingTabs(self, ergCandidate): def __fingMatchingTabs(self, ergCandidate):
path = os.path.dirname(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): if not os.path.exists(tabPath):
tabPath = None tabPath = None
return (ergCandidate, tabPath) return (ergCandidate, tabPath)
def findCandidates(self, path: str): 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] candidates = [self.__fingMatchingTabs(x) for x in candidatesErg]
return candidates return candidates
def findPreviewRoundCandidates(self, path: str): def findPreviewRoundCandidates(self, path: str):
candidates = self.__findRecursivelyCandidates(path, 'tabges.htm') candidates = self.__findRecursivelyCandidates(path, "tabges.htm")
return candidates return candidates

View File

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

View File

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

View File

@ -1,12 +1,5 @@
class Person: class Person:
def __init__( def __init__(self, firstName: str, lastName: str, club: str, group: str):
self,
firstName: str,
lastName: str,
club: str,
group: str
):
self.firstName = firstName self.firstName = firstName
self.lastName = lastName self.lastName = lastName
self.club = club self.club = club
@ -17,18 +10,19 @@ class Person:
False False
return ( return (
self.firstName == o.firstName and self.firstName == o.firstName
self.lastName == o.lastName and and self.lastName == o.lastName
self.club == o.club and and self.club == o.club
self.group == o.group and self.group == o.group
) )
def getTuple(self): def getTuple(self):
return (self.firstName, self.lastName, self.club) return (self.firstName, self.lastName, self.club)
def __repr__(self): def __repr__(self):
return f'{self.firstName} {self.lastName} ({self.club}, {self.group})' return f"{self.firstName} {self.lastName} ({self.club}, {self.group})"
def __hash__(self): def __hash__(self):
return self.firstName.__hash__() + self.lastName.__hash__() + self.club.__hash__() return (
self.firstName.__hash__() + self.lastName.__hash__() + self.club.__hash__()
)

View File

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

View File

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

View File

@ -2,12 +2,13 @@ import solo_turnier.reader
import os import os
import json import json
def test_import(): 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) reader = solo_turnier.reader.CSVResultReader(fileName)
ret = reader.readFile() 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) expected = json.load(fp)
assert ret == expected assert ret == expected

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,25 @@
from . import group from . import group
from . import competition_class from . import competition_class
class CSVResultRow: 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.firstName = firstName
self.lastName = lastName self.lastName = lastName
self.name = f'{firstName} {lastName}' self.name = f"{firstName} {lastName}"
self.club = club self.club = club
self.id = id self.id = id
self.group = group self.group = group
@ -18,7 +31,8 @@ class CSVResultRow:
self.competitionClass = competitionClass self.competitionClass = competitionClass
def __repr__(self): 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: class HtmlPreviewParticipant:
def __init__(self, name, id, group_): def __init__(self, name, id, group_):
@ -32,10 +46,16 @@ class HtmlPreviewParticipant:
if type(o) != HtmlPreviewParticipant: if type(o) != HtmlPreviewParticipant:
return False 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): def __repr__(self):
return f'{self.id} ({self.name}, {self.group})' return f"{self.id} ({self.name}, {self.group})"
def __hash__(self): def __hash__(self):
return hash((self.id, self.name, self.group)) return hash((self.id, self.name, self.group))
@ -43,6 +63,7 @@ class HtmlPreviewParticipant:
def __gt__(self, other): def __gt__(self, other):
return self.id >= other.id return self.id >= other.id
class HtmlParticipant: class HtmlParticipant:
def __init__(self, name, id): def __init__(self, name, id):
self.name = name self.name = name
@ -53,10 +74,16 @@ class HtmlParticipant:
if type(o) != HtmlPreviewParticipant: if type(o) != HtmlPreviewParticipant:
return False 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): def __repr__(self):
return f'{self.id}: {self.name}' return f"{self.id}: {self.name}"
def __hash__(self): def __hash__(self):
return hash((self.id, self.name)) return hash((self.id, self.name))
@ -64,16 +91,20 @@ class HtmlParticipant:
def __gt__(self, other): def __gt__(self, other):
return self.id >= other.id return self.id >= other.id
# class PreviewParticipationData: # class PreviewParticipationData:
# def __init__(self, dance: str, class_: competition_class.CompetitionClass): # def __init__(self, dance: str, class_: competition_class.CompetitionClass):
# self.class_ = class_ # self.class_ = class_
# self.dance = dance # self.dance = dance
class HtmlPreviewImport: class HtmlPreviewImport:
def __init__( def __init__(
self, self,
participants: dict[int, list[HtmlPreviewParticipant]], 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.participants = participants
self.results = results self.results = results
@ -81,6 +112,7 @@ class HtmlPreviewImport:
def __repr__(self): def __repr__(self):
return (str(self.participants), str(self.results)) return (str(self.participants), str(self.results))
class HtmlResultImport: class HtmlResultImport:
def __init__(self, results: dict[HtmlParticipant, str]): def __init__(self, results: dict[HtmlParticipant, str]):
self.results = results self.results = results
@ -88,6 +120,7 @@ class HtmlResultImport:
def __repr__(self): def __repr__(self):
return str(self.results) return str(self.results)
class HtmlResultTotalTable: class HtmlResultTotalTable:
def __init__(self, participants): def __init__(self, participants):
self.participants = participants self.participants = participants
@ -95,6 +128,7 @@ class HtmlResultTotalTable:
def __repr__(self): def __repr__(self):
return str(self.participants) return str(self.participants)
class HtmlCompetitionResultRow: class HtmlCompetitionResultRow:
def __init__(self, name, id, dance, group, class_, place, placeTo, finalist): def __init__(self, name, id, dance, group, class_, place, placeTo, finalist):
self.dance = dance self.dance = dance
@ -108,28 +142,30 @@ class HtmlCompetitionResultRow:
def __repr__(self): def __repr__(self):
if self.place == self.placeTo: if self.place == self.placeTo:
result = f'{self.place}.' result = f"{self.place}."
else: else:
result = f'{self.place}.-{self.placeTo}.' result = f"{self.place}.-{self.placeTo}."
if self.finalist == True: if self.finalist == True:
finalist = '[F]' finalist = "[F]"
else: else:
finalist = '' finalist = ""
return f'Result[{self.id}]({self.group} {self.class_} {self.dance} as {result}{finalist})' return f"Result[{self.id}]({self.group} {self.class_} {self.dance} as {result}{finalist})"
def __eq__(self, o): def __eq__(self, o):
if not isinstance(o, CompetitionResult): if not isinstance(o, CompetitionResult):
return False return False
return ( return (
self.dance == o.dance and self.dance == o.dance
self.competitionClass == o.competitionClass and and self.competitionClass == o.competitionClass
self.competitionGroup == o.competitionGroup and and self.competitionGroup == o.competitionGroup
self.place == o.place and self.placeTo == o.placeTo and and self.place == o.place
self.id == o.id and self.placeTo == o.placeTo
and self.id == o.id
) )
class HtmlSingleCompetitionResult: class HtmlSingleCompetitionResult:
def __init__(self, name, place, placeTo, finalist): def __init__(self, name, place, placeTo, finalist):
self.name = name self.name = name
@ -141,14 +177,14 @@ class HtmlSingleCompetitionResult:
if self.placeTo is None: if self.placeTo is None:
place = self.place place = self.place
else: else:
place = f'{self.place}-{self.placeTo}' place = f"{self.place}-{self.placeTo}"
if self.finalist: if self.finalist:
return f'Res({self.name} [F], placed {place})' return f"Res({self.name} [F], placed {place})"
else: else:
return f'Res({self.name}, placed {place})' return f"Res({self.name}, placed {place})"
def __gt__(self,other): def __gt__(self, other):
return self.id > other.id return self.id > other.id
def __eq__(self, other): def __eq__(self, other):
@ -157,18 +193,36 @@ class HtmlSingleCompetitionResult:
def __hash__(self): def __hash__(self):
return hash(self.id) return hash(self.id)
class HtmlCompetitionTotalResults: class HtmlCompetitionTotalResults:
def __init__(self): def __init__(self):
self.results = {} self.results = {}
self.tabges = {} 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) 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)] 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 = {} ret = {}
for k in self.results: for k in self.results:
@ -187,6 +241,7 @@ class HtmlCompetitionTotalResults:
l.append(result) l.append(result)
self.results[tup] = l self.results[tup] = l
class SingleParticipantResult: class SingleParticipantResult:
def __init__( def __init__(
self, self,
@ -195,7 +250,7 @@ class SingleParticipantResult:
dance: str, dance: str,
finalist: bool, finalist: bool,
place: int, place: int,
placeTo: int|None placeTo: int | None,
): ):
self.competitionClass = competitionClass self.competitionClass = competitionClass
self.nativeClass = nativeClass self.nativeClass = nativeClass
@ -211,65 +266,81 @@ class SingleParticipantResult:
self.placeNativeTo = None self.placeNativeTo = None
def __repr__(self): def __repr__(self):
asFinalist = ' as finalist' if self.finalist else '' asFinalist = " as finalist" if self.finalist else ""
if self.placeTo is None: 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): def getPlace(self):
if self.placeTo is None: if self.placeTo is None:
return f'{self.place}.' return f"{self.place}."
else: else:
return f'{self.place}.-{self.placeTo}.' return f"{self.place}.-{self.placeTo}."
def getNativePlace(self): def getNativePlace(self):
if self.placeNativeTo is None: if self.placeNativeTo is None:
return f'{self.placeNative}.' return f"{self.placeNative}."
else: else:
return f'{self.placeNative}.-{self.placeNativeTo}.' return f"{self.placeNative}.-{self.placeNativeTo}."
class TotalGroupResult: 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__( def __init__(
self, 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() parser = group.GroupParser()
self.groups = parser.getGroupsAsSortedList(resultPerGroup.keys()) self.groups = parser.getGroupsAsSortedList(resultPerGroup.keys())
self.results = resultPerGroup self.results = resultPerGroup
class State3: class State3:
def __init__( def __init__(
self, self, previewImport: HtmlPreviewImport, htmlResults: HtmlCompetitionTotalResults
previewImport: HtmlPreviewImport,
htmlResults: HtmlCompetitionTotalResults
): ):
self.previewImport = previewImport self.previewImport = previewImport
self.htmlResults = htmlResults self.htmlResults = htmlResults
class Participant: 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.firstName = firstName
self.lastName = lastName self.lastName = lastName
self.club = club self.club = club
self.group = group self.group = group
self.class_ = class_ self.class_ = class_
class ParticipantResult: class ParticipantResult:
def __init__( def __init__(
self, id: int, finalist: bool, cancelled: bool, self,
id: int,
finalist: bool,
cancelled: bool,
group: group.Group_t, group: group.Group_t,
class_: competition_class.Class_t, class_: competition_class.Class_t,
dance: str, dance: str,
place, placeTo place,
placeTo,
): ):
self.id = id self.id = id
self.finalist = finalist self.finalist = finalist
@ -280,10 +351,12 @@ class ParticipantResult:
self.place = place self.place = place
self.placeTo = placeTo self.placeTo = placeTo
class Stage2: class Stage2:
def __init__(self, results: dict[Participant, list[ParticipantResult]]): def __init__(self, results: dict[Participant, list[ParticipantResult]]):
self.results = results self.results = results
class TableCompetitionEntry: class TableCompetitionEntry:
def __init__( def __init__(
self, self,
@ -293,7 +366,7 @@ class TableCompetitionEntry:
place: int = -1, place: int = -1,
placeTo: int = -1, placeTo: int = -1,
group: group.Group_t = None, group: group.Group_t = None,
id: int = None id: int = None,
): ):
self.finalist = finalist self.finalist = finalist
self.cancelled = cancelled self.cancelled = cancelled
@ -304,58 +377,57 @@ class TableCompetitionEntry:
def __repr__(self): def __repr__(self):
def paramMerging(l): 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: if self.cancelled:
params = paramMerging([self.group, self.class_, self.id]) params = paramMerging([self.group, self.class_, self.id])
if len(params) > 0: if len(params) > 0:
return f'- ({params})' return f"- ({params})"
else: else:
return '-' return "-"
elif not self.finalist: elif not self.finalist:
params = paramMerging([self.group, self.class_, self.id]) params = paramMerging([self.group, self.class_, self.id])
if len(params) > 0: if len(params) > 0:
return f'x ({params})' return f"x ({params})"
else: else:
return 'x' return "x"
else: else:
if self.place == self.placeTo: if self.place == self.placeTo:
place = f'{self.place}.' place = f"{self.place}."
else: else:
place = f'{self.place}.-{self.placeTo}.' place = f"{self.place}.-{self.placeTo}."
params = paramMerging([self.group, self.class_, self.id]) params = paramMerging([self.group, self.class_, self.id])
return f'{place} ({params})' return f"{place} ({params})"
class TableEntry: class TableEntry:
def __init__(self, competitions: list[TableCompetitionEntry]): def __init__(self, competitions: list[TableCompetitionEntry]):
self.competitions = competitions self.competitions = competitions
def __repr__(self): def __repr__(self):
return ', '.join(self.competitions) return ", ".join(self.competitions)
class TableRow: class TableRow:
def __init__( def __init__(self, participant: Participant, id: int, entries: list[TableEntry]):
self,
participant: Participant,
id: int,
entries: list[TableEntry]
):
self.participant = participant self.participant = participant
self.id = id self.id = id
self.entries = entries self.entries = entries
def getRowList(self): def getRowList(self):
if self.id is not None: 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: 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) return [first] + map(str, self.entries)
class OutputTable: class OutputTable:
def __init__(self, dances: list[str], rows: list[TableRow]): def __init__(self, dances: list[str], rows: list[TableRow]):
self.dances = dances self.dances = dances
self.rows = rows self.rows = rows
class Stage1: class Stage1:
def __init__(self, tables: dict[group.Group, OutputTable]): def __init__(self, tables: dict[group.Group, OutputTable]):
self.tables = tables self.tables = tables

View File

@ -10,6 +10,7 @@ from .types import HtmlCompetitionResultRow as CompetitionResult
from . import types from . import types
from . import competition_class from . import competition_class
class HtmlPerson: class HtmlPerson:
def __init__(self, name, id, group): def __init__(self, name, id, group):
self.name = name self.name = name
@ -17,7 +18,7 @@ class HtmlPerson:
self.group = group self.group = group
def __repr__(self): def __repr__(self):
return f'{self.name} ({self.id}, {self.group})' return f"{self.name} ({self.id}, {self.group})"
def __eq__(self, o): def __eq__(self, o):
if not isinstance(o, HtmlPerson): if not isinstance(o, HtmlPerson):
@ -27,11 +28,12 @@ class HtmlPerson:
def __hash__(self): def __hash__(self):
return str(self).__hash__() return str(self).__hash__()
class ResultPerson: 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.firstName = firstName
self.lastName = lastName self.lastName = lastName
self.name = f'{firstName} {lastName}' self.name = f"{firstName} {lastName}"
self.club = club self.club = club
self.id = id self.id = id
self.group = group self.group = group
@ -39,9 +41,7 @@ class ResultPerson:
@staticmethod @staticmethod
def extractFromResultRow(row: ResultRow): def extractFromResultRow(row: ResultRow):
return ResultPerson( return ResultPerson(
firstName=row.firstName, firstName=row.firstName, lastName=row.lastName, club=row.club
lastName=row.lastName,
club=row.club
) )
def __eq__(self, o): def __eq__(self, o):
@ -49,17 +49,17 @@ class ResultPerson:
return False return False
return ( return (
self.firstName == o.firstName and self.firstName == o.firstName
self.lastName == o.lastName and and self.lastName == o.lastName
self.club == o.club and and self.club == o.club
self.id == o.id and self.id == o.id
) )
def __repr__(self): def __repr__(self):
if self.id is None: if self.id is None:
return f'{self.name} ({self.club})' return f"{self.name} ({self.club})"
else: else:
return f'{self.name} ({self.club}) [{self.id}]' return f"{self.name} ({self.club}) [{self.id}]"
def __hash__(self): def __hash__(self):
text = str(self) text = str(self)
@ -69,19 +69,23 @@ class ResultPerson:
class ImportNotParsableException(Exception): class ImportNotParsableException(Exception):
pass pass
ParserList_t = dict[str, html_parser.HtmlParser] ParserList_t = dict[str, html_parser.HtmlParser]
class PreviewWorker: class PreviewWorker:
def __init__(self): def __init__(self):
self.l = logging.getLogger('solo_turnier.worker.PreviewWorker') self.l = logging.getLogger("solo_turnier.worker.PreviewWorker")
self.participants = {} self.participants = {}
self.previewResults = {} self.previewResults = {}
def filterFilesPreview(self, files: list[str]) -> ParserList_t: 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 = {} ret = {}
for file in files: for file in files:
with open(file, 'r') as fp: with open(file, "r") as fp:
text = fp.read() text = fp.read()
parser = html_parser.HtmlParser(text, file) parser = html_parser.HtmlParser(text, file)
@ -89,63 +93,75 @@ class PreviewWorker:
try: try:
data = parser.guessDataFromHtmlTitle() data = parser.guessDataFromHtmlTitle()
except: 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 continue
if data['class_'] == 'Sichtung': if data["class_"] == "Sichtung":
self.l.debug(f"Found candidate in {file}. Adding to the list.") self.l.debug(f"Found candidate in {file}. Adding to the list.")
ret[file] = parser ret[file] = parser
else: 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 return ret
def __extractPersonsFromSinglePreview(self, parser: html_parser.HtmlParser): def __extractPersonsFromSinglePreview(self, parser: html_parser.HtmlParser):
imported = parser.parsePreparationRound() imported = parser.parsePreparationRound()
parser.cleanPreparationRoundImport(imported) parser.cleanPreparationRoundImport(imported)
data = imported['data'] data = imported["data"]
headerData = parser.guessDataFromHtmlTitle() headerData = parser.guessDataFromHtmlTitle()
dance = headerData['dance'] dance = headerData["dance"]
classParser = solo_turnier.competition_class.CompetitionClassParser() classParser = solo_turnier.competition_class.CompetitionClassParser()
def getRowIndexOfClass(): def getRowIndexOfClass():
return data['titles'].index('Platz von\nPlatz bis') return data["titles"].index("Platz von\nPlatz bis")
self.l.log(5, data) self.l.log(5, data)
if data['titles'][0] != 'Wertungsrichter': if data["titles"][0] != "Wertungsrichter":
self.l.fatal('Cannot parse the parsed content of the preview file.') self.l.fatal("Cannot parse the parsed content of the preview file.")
raise ImportNotParsableException('Incompatible export file') raise ImportNotParsableException("Incompatible export file")
if data['titles'][-1] == 'Startgruppe': if data["titles"][-1] == "Startgruppe":
self.l.debug('Combined competition found. Extracting group from table required.') self.l.debug(
"Combined competition found. Extracting group from table required."
)
extractGroup = True extractGroup = True
else: else:
self.l.debug('Using group from the title.') self.l.debug("Using group from the title.")
group = parser.guessDataFromHtmlTitle(imported['title'])['group'] group = parser.guessDataFromHtmlTitle(imported["title"])["group"]
extractGroup = False extractGroup = False
classRowIndex = getRowIndexOfClass() classRowIndex = getRowIndexOfClass()
for index, e in enumerate(data['table'][0]): for index, e in enumerate(data["table"][0]):
if e['text'] == '': if e["text"] == "":
# Skip empty columns # Skip empty columns
continue continue
# Extract data from column # Extract data from column
name = e['meta'] name = e["meta"]
id = int(e['text']) id = int(e["text"])
if extractGroup: if extractGroup:
group = data['table'][-1][index]['text'] group = data["table"][-1][index]["text"]
# dance = # dance =
class_ = classParser.parseClass(data['table'][classRowIndex][index]['text']) class_ = classParser.parseClass(data["table"][classRowIndex][index]["text"])
participant = types.HtmlPreviewParticipant(name, id, group) participant = types.HtmlPreviewParticipant(name, id, group)
l = self.participants.get(id, []) 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: if participant not in l:
l.append(participant) l.append(participant)
self.participants[id] = l self.participants[id] = l
@ -162,52 +178,62 @@ class PreviewWorker:
try: try:
self.__extractPersonsFromSinglePreview(parser) self.__extractPersonsFromSinglePreview(parser)
except: 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) return types.HtmlPreviewImport(self.participants, self.previewResults)
class ResultExtractor: class ResultExtractor:
def __init__(self): def __init__(self):
self.l = logging.getLogger('solo_turnier.worker.ResultExtractor') self.l = logging.getLogger("solo_turnier.worker.ResultExtractor")
self.rePlaceSingle = re.compile(' *([0-9]+) *') self.rePlaceSingle = re.compile(" *([0-9]+) *")
self.rePlaceDouble = re.compile(' *([0-9]+) *- *([0-9]+) *') self.rePlaceDouble = re.compile(" *([0-9]+) *- *([0-9]+) *")
def getAllParsers(self, files: list[tuple[str,str]]) -> ParserList_t: def getAllParsers(self, files: list[tuple[str, str]]) -> ParserList_t:
ret = {} ret = {}
classParser = competition_class.CompetitionClassParser() classParser = competition_class.CompetitionClassParser()
for filePair in files: for filePair in files:
with open(filePair[0], 'r') as fp: with open(filePair[0], "r") as fp:
text = fp.read() text = fp.read()
parser = html_parser.HtmlParser(text, filePair[0]) parser = html_parser.HtmlParser(text, filePair[0])
if filePair[1] is None: if filePair[1] is None:
parserTab = None parserTab = None
else: else:
with open(filePair[1], 'r') as fp: with open(filePair[1], "r") as fp:
textTab = fp.read() textTab = fp.read()
parserTab = html_parser.HtmlParser(textTab, filePair[1]) parserTab = html_parser.HtmlParser(textTab, filePair[1])
try: try:
data = parser.guessDataFromHtmlTitle() data = parser.guessDataFromHtmlTitle()
except: 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 continue
try: try:
guessedClass = classParser.parseClass(data['class_']) guessedClass = classParser.parseClass(data["class_"])
except: 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 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) ret[filePair] = (parser, parserTab)
return ret return ret
def _extractPlace(self, placeStr: str): def _extractPlace(self, placeStr: str):
s = placeStr.replace('.', '') s = placeStr.replace(".", "")
matches = self.rePlaceSingle.fullmatch(s) matches = self.rePlaceSingle.fullmatch(s)
if matches is not None: if matches is not None:
@ -218,60 +244,78 @@ class ResultExtractor:
return (int(matches.group(1)), int(matches.group(2))) return (int(matches.group(1)), int(matches.group(2)))
self.l.error('Could not parse place string "%s"', placeStr) 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() data = parser.guessDataFromHtmlTitle()
competitionClass = data['class_'] competitionClass = data["class_"]
competitionGroup = data['group'] competitionGroup = data["group"]
dance = data['dance'] dance = data["dance"]
result = parser.parseResult() 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(): for person in result.results.keys():
placeStr = result.results[person] placeStr = result.results[person]
place, placeTo = self._extractPlace(placeStr) place, placeTo = self._extractPlace(placeStr)
competitionResult = types.HtmlSingleCompetitionResult(person.name, place, placeTo, person.finalist) competitionResult = types.HtmlSingleCompetitionResult(
results.add(competitionGroup, competitionClass, dance, person.id, competitionResult) 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() data = parser.guessDataFromHtmlTitle()
competitionClass = data['class_'] competitionClass = data["class_"]
competitionGroup = data['group'] competitionGroup = data["group"]
dance = data['dance'] dance = data["dance"]
result = parser.parseIndividualResult(competitionGroup, competitionClass, 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) results.tabges.update(result.participants)
def extractAllData(self, parsers: ParserList_t) -> types.HtmlCompetitionTotalResults: def extractAllData(
self, parsers: ParserList_t
) -> types.HtmlCompetitionTotalResults:
ret = types.HtmlCompetitionTotalResults() ret = types.HtmlCompetitionTotalResults()
for fileNameTuple in parsers: for fileNameTuple in parsers:
fileName = fileNameTuple[0] 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) self._analyzeSingleParser(parsers[fileNameTuple][0], ret)
if parsers[fileNameTuple][1] is None: 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: 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) self._analyzeIndividualResults(parsers[fileNameTuple][1], ret)
return ret return ret
class DataWorker: class DataWorker:
def __init__(self): def __init__(self):
self.l = logging.getLogger('solo_turnier.worker.DataWorker') self.l = logging.getLogger("solo_turnier.worker.DataWorker")
def combineRowsByPerson(self, rows: list[ResultRow]) -> dict[ResultPerson, list[CompetitionResult]]: def combineRowsByPerson(
self, rows: list[ResultRow]
) -> dict[ResultPerson, list[CompetitionResult]]:
ret = {} ret = {}
for row in rows: for row in rows:
result = CompetitionResult.extractFromResultRow(row) result = CompetitionResult.extractFromResultRow(row)
if result.place == '-' or result.placeTo == '-': if result.place == "-" or result.placeTo == "-":
continue continue
person = ResultPerson.extractFromResultRow(row) person = ResultPerson.extractFromResultRow(row)
@ -297,26 +341,31 @@ 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 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 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 ambiguous = False
warnChange = False warnChange = False
unambiguousGroups = set(['Kin.', 'Jun.', 'Jug.']) unambiguousGroups = set(["Kin.", "Jun.", "Jug."])
combinations = set(['Kin./Jun.', 'Jun./Jug.']) combinations = set(["Kin./Jun.", "Jun./Jug."])
for person in data: for person in data:
groupsRaw = set([c.group for c in data[person]]) groupsRaw = set([c.group for c in data[person]])
unknown = groupsRaw.difference(unambiguousGroups).difference(combinations) unknown = groupsRaw.difference(unambiguousGroups).difference(combinations)
if len(unknown) > 0: 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)) numUnambiguousGroups = len(groupsRaw.intersection(unambiguousGroups))
if numUnambiguousGroups == 0: if numUnambiguousGroups == 0:
if len(groupsRaw) == 2: if len(groupsRaw) == 2:
warnChange = True warnChange = True
person.group = 'Jun.' person.group = "Jun."
else: else:
ambiguous = True ambiguous = True
if len(groupsRaw) == 1: if len(groupsRaw) == 1:
@ -329,37 +378,57 @@ class DataWorker:
person.group = list(groupsRaw.intersection(unambiguousGroups))[0] person.group = list(groupsRaw.intersection(unambiguousGroups))[0]
else: else:
raise Exception(f'{person} cannot have different groups.') raise Exception(f"{person} cannot have different groups.")
return (not ambiguous, warnChange) return (not ambiguous, warnChange)
def _createHtmlLUT(self, htmlImports: list[html_parser.HtmlImport]): def _createHtmlLUT(self, htmlImports: list[html_parser.HtmlImport]):
ret = {} ret = {}
parser = html_parser.HtmlParser('') parser = html_parser.HtmlParser("")
for imp in htmlImports: for imp in htmlImports:
parsed = parser.guessDataFromHtmlTitle(imp.title) parsed = parser.guessDataFromHtmlTitle(imp.title)
key = (parsed['group'], parsed['class_'], parsed['dance']) key = (parsed["group"], parsed["class_"], parsed["dance"])
ret[key] = imp ret[key] = imp
self.l.debug('LUT[%s] = %s', key, imp) self.l.debug("LUT[%s] = %s", key, imp)
self.l.debug('LUT completed') self.l.debug("LUT completed")
return ret 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) lut = self._createHtmlLUT(htmlImports)
for person in data: for person in data:
for competition in data[person]: for competition in data[person]:
key = (competition.competitionGroup, competition.competitionClass, competition.dance) key = (
competition.competitionGroup,
competition.competitionClass,
competition.dance,
)
htmlImport = lut[key] htmlImport = lut[key]
participant = htmlImport.participants[str(competition.id)] participant = htmlImport.participants[str(competition.id)]
if participant.name != person.name: 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 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 = [ allDances = [
'Samba', 'Cha Cha', 'Rumba', 'Paso Doble', 'Jive', "Samba",
'Langs. Walzer', 'Tango', 'Wiener Walzer', 'Slowfox', 'Quickstep' "Cha Cha",
"Rumba",
"Paso Doble",
"Jive",
"Langs. Walzer",
"Tango",
"Wiener Walzer",
"Slowfox",
"Quickstep",
] ]
dancesPresent = {d: False for d in allDances} dancesPresent = {d: False for d in allDances}
@ -369,21 +438,24 @@ class DataWorker:
return [d for d in allDances if dancesPresent[d]] 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 = { groups = {
'Kin.': [p for p in data.keys() if p.group == 'Kin.'], "Kin.": [p for p in data.keys() if p.group == "Kin."],
'Jun.': [p for p in data.keys() if p.group == 'Jun.'], "Jun.": [p for p in data.keys() if p.group == "Jun."],
'Jug.': [p for p in data.keys() if p.group == 'Jug.'], "Jug.": [p for p in data.keys() if p.group == "Jug."],
} }
found = groups['Kin.'] + groups['Jun.'] + groups['Jug.'] found = groups["Kin."] + groups["Jun."] + groups["Jug."]
groups['Sonst'] = [p for p in data.keys() if p not in found] groups["Sonst"] = [p for p in data.keys() if p not in found]
return groups return groups
def sortPersonsInGroup(self, persons: list[ResultPerson]) -> list[ResultPerson]: def sortPersonsInGroup(self, persons: list[ResultPerson]) -> list[ResultPerson]:
ids = [p.id for p in persons] ids = [p.id for p in persons]
def decorateByName(p: ResultPerson): def decorateByName(p: ResultPerson):
return (f'{p.name} ({p.club})', p) return (f"{p.name} ({p.club})", p)
def decorateById(p: ResultPerson): def decorateById(p: ResultPerson):
return (p.id, p) return (p.id, p)
@ -399,91 +471,101 @@ class DataWorker:
return ([d[1] for d in decorated], showIds) 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 = [] ret = []
for dance in dances: for dance in dances:
competitions = [c for c in results if c.dance == dance] competitions = [c for c in results if c.dance == dance]
if len(competitions) == 0: if len(competitions) == 0:
ret.append(None) ret.append(None)
elif len(competitions) > 1: 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: else:
ret.append(competitions[0]) ret.append(competitions[0])
return ret return ret
class Worker: class Worker:
def __init__(self): def __init__(self):
self.l = logging.getLogger('solo_turnier.worker.Worker') self.l = logging.getLogger("solo_turnier.worker.Worker")
self._allDances = ( self._allDances = ["Samba", "Cha Cha", "Rumba", "Paso Doble", "Jive"] + [
['Samba', 'Cha Cha', 'Rumba', 'Paso Doble', 'Jive'] + "Langs. Walzer",
['Langs. Walzer', 'Tango', 'Wiener Walzer', 'Slowfox', 'Quickstep'] "Tango",
) "Wiener Walzer",
"Slowfox",
"Quickstep",
]
def collectAllData( def collectAllData(
self, self, htmlCandidatesPreview: list[str], htmlResultsFileNames: list[str]
htmlCandidatesPreview: list[str],
htmlResultsFileNames: list[str]
) -> types.State3: ) -> types.State3:
previewWorker = PreviewWorker() previewWorker = PreviewWorker()
self.l.info('Filtering for pure preview rounds.') self.l.info("Filtering for pure preview rounds.")
parsers = previewWorker.filterFilesPreview(htmlCandidatesPreview) parsers = previewWorker.filterFilesPreview(htmlCandidatesPreview)
self.l.debug('Remaining files: %s', list(parsers.keys())) self.l.debug("Remaining files: %s", list(parsers.keys()))
self.l.info('Extracting person data from the preview rounds.') self.l.info("Extracting person data from the preview rounds.")
previewImport = previewWorker.importAllData(parsers) previewImport = previewWorker.importAllData(parsers)
self.l.debug('Total preview imported participants: %s', pformat(previewImport.participants)) self.l.debug(
self.l.log(5, 'Total preview results: %s', pformat(previewImport.results)) "Total preview imported participants: %s",
pformat(previewImport.participants),
)
self.l.log(5, "Total preview results: %s", pformat(previewImport.results))
resultExtractor = ResultExtractor() resultExtractor = ResultExtractor()
resultParsers = resultExtractor.getAllParsers(htmlResultsFileNames) resultParsers = resultExtractor.getAllParsers(htmlResultsFileNames)
htmlResults = resultExtractor.extractAllData(resultParsers) 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) return types.State3(previewImport, htmlResults)
def combineData(self, importedData: types.State3): 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) 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 = {} totalResult = {}
for group in groups: 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) 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) participants = self._extractParticipantsPerGroup(importedData, group)
self.l.log(5, 'Related participants %s', participants) self.l.log(5, "Related participants %s", participants)
results = {} results = {}
for participant in participants: 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( resultsOfParticipant = self._getResultOfSingleParticipant(
participant, group, importedData.previewImport, participant,
importedData.htmlResults, dances group,
importedData.previewImport,
importedData.htmlResults,
dances,
) )
self.l.log(5, 'Obtained result %s', resultsOfParticipant) self.l.log(5, "Obtained result %s", resultsOfParticipant)
results[participant] = 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._fixNativePlaces(dances, results)
self._fixNativeDataFromTable(dances, results, importedData.htmlResults) 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) # self.l.log(5,'Fixed data %s', results)
totalResult[group] = types.TotalGroupResult(dances, 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) ret = types.State4(totalResult)
return ret return ret
def _extractGroups(self, data: types.State3): def _extractGroups(self, data: types.State3):
groupParser = solo_turnier.group.GroupParser() groupParser = solo_turnier.group.GroupParser()
@ -498,11 +580,13 @@ class Worker:
groupSet.update(gr.getContainedGroups()) groupSet.update(gr.getContainedGroups())
# self.l.log(5, 'Group type %s', type(gr)) # 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) groups = groupParser.getGroupsAsSortedList(groupSet)
return groups 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() groupParser = solo_turnier.group.GroupParser()
dances = set() dances = set()
@ -518,7 +602,10 @@ class Worker:
additionalDances.update(foundDances.difference(self._allDances)) additionalDances.update(foundDances.difference(self._allDances))
if len(additionalDances) > 0: 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] dancesList = [x for x in self._allDances if x in dances]
additionalDancesList = list(additionalDances) additionalDancesList = list(additionalDances)
@ -529,7 +616,7 @@ class Worker:
self, self,
importedData: types.State3, importedData: types.State3,
# previewData: types.HtmlPreviewImport, # previewData: types.HtmlPreviewImport,
group: solo_turnier.group.Group group: solo_turnier.group.Group,
) -> list[types.HtmlPreviewParticipant]: ) -> list[types.HtmlPreviewParticipant]:
groupParser = types.group.GroupParser() groupParser = types.group.GroupParser()
@ -546,14 +633,14 @@ class Worker:
fixture = importedData.htmlResults.tabges[tup] fixture = importedData.htmlResults.tabges[tup]
if fixture[2] is not None and fixture[2] != group: 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 continue
part = importedData.htmlResults.results[tup][0] part = importedData.htmlResults.results[tup][0]
part.id = int(tup[3]) part.id = int(tup[3])
ret.append(part) ret.append(part)
self.l.log(5, 'ret %s', ret) self.l.log(5, "ret %s", ret)
# raise Exception('Test') # raise Exception('Test')
# for id in previewData.participants: # for id in previewData.participants:
@ -569,23 +656,25 @@ class Worker:
nominalGroup: solo_turnier.group.Group, nominalGroup: solo_turnier.group.Group,
previewResults: types.HtmlPreviewImport, previewResults: types.HtmlPreviewImport,
totalResults: types.HtmlCompetitionTotalResults, totalResults: types.HtmlCompetitionTotalResults,
allDances: list[str] allDances: list[str],
) -> list[types.SingleParticipantResult|None]: ) -> list[types.SingleParticipantResult | None]:
rawResults = totalResults.getById(participant.id) 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] results = [None for x in allDances]
for danceIdx, dance in enumerate(allDances): for danceIdx, dance in enumerate(allDances):
# self.l.log(5, '%s %s', dance, danceIdx) # self.l.log(5, '%s %s', dance, danceIdx)
def getResult() -> types.SingleParticipantResult|None: def getResult() -> types.SingleParticipantResult | None:
for key in rawResults: for key in rawResults:
if key[0] != dance: if key[0] != dance:
continue continue
rawResult = rawResults[key] rawResult = rawResults[key]
if len(rawResult) != 1: if len(rawResult) != 1:
raise Exception('Multiple results found with same key') raise Exception("Multiple results found with same key")
rawResult = rawResult[0] rawResult = rawResult[0]
nativeClass = key[2] nativeClass = key[2]
@ -594,8 +683,12 @@ class Worker:
# self.l.log(5, 'Result %s => %s', key, rawResult) # self.l.log(5, 'Result %s => %s', key, rawResult)
ret = types.SingleParticipantResult( ret = types.SingleParticipantResult(
key[2], nativeClass, dance, rawResult.finalist, key[2],
rawResult.place, rawResult.placeTo nativeClass,
dance,
rawResult.finalist,
rawResult.place,
rawResult.placeTo,
) )
return ret return ret
@ -609,18 +702,18 @@ class Worker:
self, self,
dances: list[str], dances: list[str],
data: dict[types.HtmlPreviewParticipant, list[types.SingleParticipantResult]], data: dict[types.HtmlPreviewParticipant, list[types.SingleParticipantResult]],
importedData: types.HtmlCompetitionTotalResults importedData: types.HtmlCompetitionTotalResults,
): ):
rePlace = re.compile('([0-9]+)(?:-([0-9]+))?') rePlace = re.compile("([0-9]+)(?:-([0-9]+))?")
classParser = competition_class.CompetitionClassParser() classParser = competition_class.CompetitionClassParser()
for participant in data.keys(): for participant in data.keys():
self.l.log(5, 'fixing participant %s', participant) self.l.log(5, "fixing participant %s", participant)
results = data[participant] results = data[participant]
for result in results: for result in results:
if result is None: if result is None:
continue continue
self.l.log(5, 'Looking at result set %s', result) self.l.log(5, "Looking at result set %s", result)
def selectEntry(k): def selectEntry(k):
return k[2] == result.dance and int(k[3]) == participant.id return k[2] == result.dance and int(k[3]) == participant.id
@ -633,13 +726,19 @@ class Worker:
continue continue
raw = importedData.tabges[keys[selectedIndex]] raw = importedData.tabges[keys[selectedIndex]]
self.l.log(5,'Raw %s', raw) self.l.log(5, "Raw %s", raw)
nativePlaceRaw = raw[0] nativePlaceRaw = raw[0]
matcher = rePlace.fullmatch(nativePlaceRaw) matcher = rePlace.fullmatch(nativePlaceRaw)
if matcher is None: 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 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.placeNative = matcher.group(1)
result.placeNativeTo = matcher.group(2) result.placeNativeTo = matcher.group(2)
@ -651,7 +750,7 @@ class Worker:
def _fixNativePlaces( def _fixNativePlaces(
self, self,
dances: list[str], dances: list[str],
data: dict[types.HtmlPreviewParticipant, list[types.SingleParticipantResult]] data: dict[types.HtmlPreviewParticipant, list[types.SingleParticipantResult]],
): ):
classParser = solo_turnier.competition_class.CompetitionClassParser() classParser = solo_turnier.competition_class.CompetitionClassParser()
allClasses = classParser.getAllClasses() allClasses = classParser.getAllClasses()
@ -659,7 +758,9 @@ class Worker:
for class_ in allClasses: for class_ in allClasses:
for danceIdx, dance in enumerate(dances): 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 = [] remainingParticipants = []
@ -676,7 +777,9 @@ class Worker:
# self.l.log(5, 'Skipping %s as the native class is higher', participant) # self.l.log(5, 'Skipping %s as the native class is higher', participant)
continue continue
remainingParticipants.append((danceResult.place, participant.id, participant)) remainingParticipants.append(
(danceResult.place, participant.id, participant)
)
remainingParticipants.sort() remainingParticipants.sort()
# self.l.log(5, 'Remaining participants %s', remainingParticipants) # self.l.log(5, 'Remaining participants %s', remainingParticipants)
@ -684,7 +787,10 @@ class Worker:
def getAllParticipantsWithSamePlace(): def getAllParticipantsWithSamePlace():
first = remainingParticipants.pop(0) first = remainingParticipants.pop(0)
ret = [first] 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)) ret.append(remainingParticipants.pop(0))
return ret return ret
@ -711,26 +817,31 @@ class Worker:
def filterOutFinalists(self, data: types.State4, filterOut: bool): def filterOutFinalists(self, data: types.State4, filterOut: bool):
for group in data.results: 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() participants = data.results[group].results.keys()
droppedParticipants = [] droppedParticipants = []
for participant in participants: 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: if x is None:
return False return False
return x.finalist 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 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: if finalist:
participant.finalist = True participant.finalist = True
else: else:
participant.finalist = False 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) droppedParticipants.append(participant)
if filterOut: if filterOut: