PyScore

001: # -*- python -*-
002: #### database.py #### python package PyScore.tabulate module database ####
003: 
004: # PyScore
005: # a race scoring programme
006: # written by Matt Draisey
007: # 2004 April 6
008: 
009: reloadables=[]
010: 
011: #### database.py #### python package PyScore.tabulate module database ####
012: 
013: import bisect
014: from relational import base
015: from standing import boatconf,confbase,eventstat
016: from tabulate import racesheet,scoresheet,printsheet
017: from tabulate import handicap,series,championship,casual
018: from utility.verbose import vv,vvv,vvvv,debug
019: 
020: ######## ######## data base class components ######## ########
021: 
022: class ConfBase(object):
023:     """
024:     Basic configuration and pre-registration data.
025:     """
026: 
027:     def __init__(self):
028:         self.boatdict=confbase.ConfDict()
029:         self.configurationlist=confbase.ConfList()
030:         
031:         self.seriesnamelist=confbase.ConfList(eventstat.SERIESNAMES)
032:         self.racenamelist=confbase.ConfList(eventstat.RACENAMES)
033:                 
034:     def init_boat(self,sailnumber,boatname):
035:         assert isinstance(sailnumber,str)
036:         assert isinstance(boatname,str)
037: 
038:         b=boatconf.canonical_boat(sailnumber,boatname)
039: 
040:         if b in self.boatdict:
041:             boat=self.boatdict[b]
042:         else:
043:             boat=boatconf.BoatIdentificationEntry(sailnumber,boatname)
044:             self.boatdict[b]=boat
045: 
046:         return boat
047: 
048:     def import_boatconf(self,boatconffile):
049:         """Import registered boat configuration database."""
050: 
051:         if isinstance(boatconffile,file):
052:             try:
053:                 timesheet=[]
054:                 noglobals={"__builtins__":None}
055:                 for line in boatconffile:
056:                     timesheet.append(eval(line,noglobals))
057:             except (EOFError,SyntaxError):
058:                 timesheet=[]
059: 
060:         self.configurationlist.sort()
061: 
062: class SheetBase(object):
063:     """
064:     A data base container for race, start and handicap sheets.
065:     Also contains dictionaries for fast look-ups, and init factories
066:     with new object routines that reuse existing objects and maintain
067:     the overall logical data structure.
068:     """
069: 
070:     def __init__(self):
071:         self.racesheet=racesheet.SheetDict()
072:         self.startsheet=racesheet.SheetDict()
073:         self.handicapsheet=racesheet.SheetList()
074:         self.rcsheet=racesheet.SheetList()
075:         # handicapsheet and rcsheet maintained in canonical order       
076: 
077:     def racesheet_factories(self,factoryrace,factorydivision):
078:         """
079:         Closures are returned as factories with default arguments.
080:         'init' routines initialize just created objects,
081:         'new' routines only create objects not previously created.
082:         """
083: 
084:         def newRaceSheetEntry(rname):
085:             assert isinstance(rname,eventstat.RaceName)
086: 
087:             if rname in self.racesheet:
088:                 race=self.racesheet[rname]
089:             else:
090:                 race=racesheet.RaceSheetEntry(rname)
091:                 self.racesheet[rname]=race
092: 
093:             return race
094: 
095:         def initStartSheetEntry(d,r=None):
096:             assert isinstance(d,eventstat.DivisionName)
097: 
098:             r=r or factoryrace
099:             if isinstance(r,racesheet.RaceSheetEntry):
100:                 return racesheet.StartSheetEntry(r,d)
101:             assert isinstance(r,eventstat.RaceName)
102: 
103:             if r in self.racesheet:
104:                 race=self.racesheet[r]
105:             else:
106:                 race=racesheet.RaceSheetEntry(r)
107:                 self.racesheet[r]=race
108: 
109:             return racesheet.StartSheetEntry(race,d)
110: 
111:         def newStartSheetEntry(d,r=None):
112:             assert isinstance(d,eventstat.DivisionName)
113: 
114:             r=r or factoryrace
115:             if isinstance(r,racesheet.RaceSheetEntry):
116:                 rname=r.racename
117:             else:
118:                 rname=r
119:             assert isinstance(rname,eventstat.RaceName)
120: 
121:             if (rname,d) in self.startsheet:
122:                 start=self.startsheet[rname,d]
123:             else:
124:                 start=initStartSheetEntry(d,r)
125:                 self.startsheet[rname,d]=start
126: 
127:             return start
128: 
129:         return (
130:             newRaceSheetEntry,
131:             initStartSheetEntry,
132:             newStartSheetEntry,
133:         )
134: 
135:     def new_racesheet(self,race=None):
136:         return self.racesheet_factories(race,None)[0]
137: 
138:     def new_startsheet(self,race=None,division=None):
139:         return self.racesheet_factories(race,division)[2]
140: 
141: class SeriesBase(object):
142:     """
143:     A data base container for series, seriesdivision and seriesscore
144:     sheets.
145:     Also contains dictionaries for fast look-ups, and init factories
146:     with new object routines that reuse existing objects and maintain
147:     the overall logical data structure.
148:     """
149: 
150:     def __init__(self):
151:         self.seriestab=scoresheet.ScoreDict()
152:         self.seriesdivisiontab=scoresheet.ScoreDict()
153:         self.seriesscoretab=scoresheet.ScoreList()
154:         # seriesscoretab maintained in canonical order  
155: 
156:     def scoresheet_factories(self,factoryseries,factorydivision):
157:         """
158:         Closures are returned as factories with default arguments.
159:         'init' routines initialize just created objects,
160:         'new' routines only create objects not previously created.
161:         """
162: 
163:         def initSeriesDivisionEntry(d,s=None):
164:             assert isinstance(d,eventstat.DivisionName)
165: 
166:             s=s or factoryseries
167:             if isinstance(s,scoresheet.SeriesEntry):
168:                 return scoresheet.SeriesDivisionEntry(s,d)
169:             assert isinstance(s,eventstat.SeriesName)
170: 
171:             if s in self.seriestab:
172:                 series=self.seriestab[s]
173:             else:
174:                 series=scoresheet.SeriesEntry(s)
175:                 self.seriestab[s]=series
176: 
177:             return scoresheet.SeriesDivisionEntry(series,d)
178: 
179:         def newSeriesDivisionEntry(d,s=None):
180:             assert isinstance(d,eventstat.DivisionName)
181: 
182:             s=s or factoryseries
183:             if isinstance(s,scoresheet.SeriesEntry):
184:                 sname=s.seriesname
185:             else:
186:                 sname=s
187:             assert isinstance(sname,eventstat.SeriesName)
188: 
189:             if (sname,d) in self.seriesdivisiontab:
190:                 seriesdivision=self.seriesdivisiontab[sname,d]
191:             else:
192:                 seriesdivision=initSeriesDivisionEntry(d,s)
193:                 self.seriesdivisiontab[sname,d]=seriesdivision
194: 
195:             return seriesdivision
196: 
197:         def initSeriesScoreEntry(boat,d=None,s=None):
198:             assert isinstance(boat,boatconf.BoatIdentificationEntry)
199: 
200:             d=d or factorydivision
201:             if isinstance(d,scoresheet.SeriesDivisionEntry):
202:                 return scoresheet.SeriesScoreEntry(d,boat)
203:             assert isinstance(d,eventstat.DivisionName)
204: 
205:             s=s or factoryseries
206:             if isinstance(s,scoresheet.SeriesEntry):
207:                 sname=s.seriesname
208:             else:
209:                 sname=s
210:             assert isinstance(sname,eventstat.SeriesName)
211: 
212:             if (sname,d) in self.seriesdivisiontab:
213:                 seriesdivision=self.seriesdivisiontab[sname,d]
214:             else:
215:                 seriesdivision=initSeriesDivisionEntry(d,s)
216:                 self.seriesdivisiontab[sname,d]=seriesdivision
217: 
218:             return scoresheet.SeriesScoreEntry(
219:                 seriesdivision,boat
220:             )
221: 
222:         def newSeriesScoreEntry(boat,d=None,s=None):
223:             seriesscore=initSeriesScoreEntry(boat,d,s)
224:             self.seriesscoretab.append(seriesscore)
225:             return seriesscore
226: 
227:         return (
228:             initSeriesDivisionEntry,
229:             newSeriesDivisionEntry,
230:             initSeriesScoreEntry,
231:             newSeriesScoreEntry,
232:         )
233: 
234:     def new_seriesdivision(self,series=None,division=None):
235:         return self.scoresheet_factories(series,division)[1]
236: 
237:     def new_seriesscore(self,series=None,division=None):
238:         return self.scoresheet_factories(series,division)[3]
239: 
240: class ChampionshipBase(object):
241:     """
242:     A data base container for championship, championshipdivision and
243:     championshipscore sheets.
244:     Also contains dictionaries for fast look-ups, and init factories
245:     with new object routines that reuse existing objects and maintain
246:     the overall logical data structure.
247:     """
248: 
249:     def __init__(self):
250:         self.championship=scoresheet.ChampionshipEntry(
251:             eventstat.ChampionshipName.YEAR
252:         )
253:         self.championshipdivisiontab=scoresheet.ScoreDict()
254:         self.championshipscoretab=scoresheet.ScoreList()
255:         # championshipscoretab maintained in canonical order    
256:             
257:     def scoresheet_factories(self,factorydivision):
258:         """
259:         Closures are returned as factories with default arguments.
260:         'init' routines initialize just created objects,
261:         'new' routines only create objects not previously created.
262:         """
263: 
264:         def initChampionshipDivisionEntry(d):
265:             assert isinstance(d,eventstat.DivisionName)
266: 
267:             return scoresheet.ChampionshipDivisionEntry(self.championship,d)
268: 
269:         def newChampionshipDivisionEntry(d):
270:             assert isinstance(d,eventstat.DivisionName)
271: 
272:             if d in self.championshipdivisiontab:
273:                 champdivision=self.championshipdivisiontab[d]
274:             else:
275:                 champdivision=initChampionshipDivisionEntry(d)
276:                 self.championshipdivisiontab[d]=champdivision
277: 
278:             return champdivision
279: 
280:         def initChampionshipScoreEntry(boat,d=None):
281:             assert isinstance(boat,boatconf.BoatIdentificationEntry)
282: 
283:             d=d or factorydivision
284:             if isinstance(d,scoresheet.ChampionshipDivisionEntry):
285:                 return scoresheet.ChampionshipScoreEntry(d,boat)
286:             assert isinstance(d,eventstat.DivisionName)
287: 
288:             if d in self.championshipdivisiontab:
289:                 champdivision=self.championshipdivisiontab[d]
290:             else:
291:                 champdivision=initChampionshipDivisionEntry(d)
292:                 self.championshipdivisiontab[d]=champdivision
293: 
294:             return scoresheet.ChampionshipScoreEntry(champdivision,boat)
295: 
296:         def newChampionshipScoreEntry(boat,d=None):
297:             champscore=initChampionshipScoreEntry(boat,d)
298:             self.championshipscoretab.append(champscore)
299:             return champscore
300: 
301:         return (
302:             initChampionshipDivisionEntry,
303:             newChampionshipDivisionEntry,
304:             initChampionshipScoreEntry,
305:             newChampionshipScoreEntry,
306:         )
307: 
308:     def new_championshipdivision(self,division=None):
309:         return self.scoresheet_factories(division)[1]
310: 
311:     def new_championshipscore(self,division=None):
312:         return self.scoresheet_factories(division)[3]
313: 
314: class CasualBase(object):
315:     """
316:     A data base container for annual casual score sheets.
317:     Also init factories to maintain the overall logical data structure.
318:     """
319: 
320:     def __init__(self):
321:         self.casual=scoresheet.CasualEntry(eventstat.CasualName.YEAR)
322:         self.midfleetscoretab=scoresheet.ScoreList()
323:         # midfleetscoretab maintained in canonical order        
324: 
325:     def init_midfleetscore(self,boat):
326:         assert isinstance(boat,boatconf.BoatIdentificationEntry)
327:         return scoresheet.MidFleetScoreEntry(self.casual,boat)
328: 
329:     def new_midfleetscore(self,boat):
330:         midfleetscore=self.init_midfleetscore(boat)
331:         self.midfleetscoretab.append(midfleetscore)
332:         return midfleetscore
333: 
334: ######## ######## the overall database ######## ########
335: 
336: class DataBase(object):
337:     """The main scoring database."""
338: 
339:     assume_all_division_starts=True
340: 
341:     def __init__(self):
342:         self.confbase=ConfBase()
343:         self.sheetbase=SheetBase()
344:         self.seriesbase=SeriesBase()
345:         self.championshipbase=ChampionshipBase()
346:         self.casualbase=CasualBase()
347: 
348:     #from standing.loadfile import load_database,save_database
349:     from standing.loadfile import import_all_timesheets
350: 
351:     def get_racename(self,s,r):
352:         assert isinstance(s,int) and 1<=s<=3
353:         assert isinstance(r,int) and 1<=r<=6
354:         return self.confbase.racenamelist[(s-1)*6+(r-1)]
355: 
356:     def import_timesheet(
357:         self,racename,racesheetf,startsheetf,timesheetf,ubersheetf
358:     ):
359:         """Import a single race timesheet."""
360: 
361:         race=self.sheetbase.new_racesheet()(racename)
362: 
363:         if isinstance(racesheetf,file):
364:             try:
365:                 try:
366:                     noglobals={"__builtins__":None}
367:                     l=racesheetf.readline()
368: 
369:                     # bs: RC boat sail number
370:                     # bn: RC boat name
371:                     # ab: abandoned
372:                     # ds: distance
373:                     # ps: comment
374: 
375:                     [bs,bn,ab,ds,ps]=eval(l,noglobals)
376:                 finally:
377:                     racesheetf.close()
378:             except (EOFError,SyntaxError):
379:                 pass
380:             else:
381:                 if bs:
382:                     bisect.insort(
383:                         self.sheetbase.rcsheet,
384:                         racesheet.RCSheetEntry(race,bs,bn,ps),
385:                     )
386:                     # maintain rcsheets order
387:                 if ab:
388:                     race.abandoned=True
389:                 if ds:
390:                     try:
391:                         race.distance=float(ds)
392:                     except ValueError:
393:                         race.distance=6.62
394:         else:
395:             race.distance=6.62
396: 
397:         newstart=self.sheetbase.new_startsheet(race)
398: 
399:         if self.assume_all_division_starts:
400:             for d in eventstat.DIVISIONNAMES[:-1]:
401:                 newstart(d)
402: 
403:         if isinstance(startsheetf,file):
404:             try:
405:                 try:
406:                     noglobals={"__builtins__":None}
407:                     startsheet=[eval(l,noglobals) for l in startsheetf]
408:                 finally:
409:                     startsheetf.close()
410:             except (EOFError,SyntaxError):
411:                 pass
412:             else:
413:                 # cd: class division
414:                 # cs: class start time
415:                 # ab: abandoned
416:                 # ds: distance
417:                 # ps: comment
418: 
419:                 for [cd,cs,ab,ds,ps] in startsheet:
420:                     start=newstart(eventstat.DivisionName(cd,strict=True))
421:                     if ab:
422:                         start.abandoned=True
423:                     if ds:
424:                         try:
425:                             start.distance=float(ds)
426:                         except ValueError:
427:                             start.distance=6.62
428: 
429:         if isinstance(timesheetf,file):
430:             try:
431:                 try:
432:                     noglobals={"__builtins__":None}
433:                     timesheet=[eval(l,noglobals) for l in timesheetf]
434:                 finally:
435:                     timesheetf.close()
436:             except (EOFError,SyntaxError):
437:                 handicaprace=racesheet.SheetList([])
438:             else:
439:                 printsheet.print_timesheet(race,timesheet,vvvv)
440: 
441:                 startfactory=self.sheetbase.new_startsheet(race)
442:                 handicaprace=racesheet.SheetList([
443:                     racesheet.HandicapSheetEntry(race,startfactory,*row)
444:                     for row in timesheet
445:                 ])
446:                 handicaprace.sort()
447:                 handicap.tabulate_all_races(handicaprace)
448: 
449:         if isinstance(ubersheetf,file):
450:             try:
451:                 try:
452:                     noglobals={"__builtins__":None}
453:                     ubersheet=[eval(l,noglobals) for l in ubersheetf]
454:                 finally:
455:                     ubersheetf.close()
456:             except (EOFError,SyntaxError):
457:                 pass
458:             else:
459:                 printsheet.print_override_scoresheet(race,ubersheet,vvvv)
460: 
461:                 for row in ubersheet:
462:                     override=racesheet.OverrideSheetEntry(
463:                         race,startfactory,*row
464:                     )
465:                     lo=bisect.bisect_left(
466:                         self.sheetbase.handicapsheet,override,
467:                     )
468:                     hi=bisect.bisect_right(
469:                         self.sheetbase.handicapsheet,override,lo,
470:                     )
471:                     handicaprace[lo:hi]=[override]
472: 
473:         if handicaprace:
474:             # timesheets may not be presented in order
475:             i=bisect.bisect(self.sheetbase.handicapsheet,handicaprace[0])
476:             self.sheetbase.handicapsheet[i:i]=handicaprace
477:             # merge timesheets maintaining canonical order
478: 
479:     def tabulate_all(self):
480:         self.tabulate_all_races()
481:         self.tabulate_all_series()
482:         self.tabulate_all_championship()
483:         self.tabulate_all_casual()
484: 
485:     def tabulate_all_races(self):
486:         """
487:         A null method.  races are tabulated when imported.
488:         """
489: 
490:     def tabulate_all_series(self):
491:         series.tabulate_all_series(
492:             self.sheetbase.racesheet.values(),
493:             self.sheetbase.startsheet.values(),
494:             self.sheetbase.handicapsheet,
495:             self.sheetbase.rcsheet,
496:             self.seriesbase.seriestab,
497:             self.seriesbase.new_seriesdivision,
498:             self.seriesbase.seriesdivisiontab,
499:             self.seriesbase.new_seriesscore,
500:             self.seriesbase.seriesscoretab,
501:         )
502: 
503:     def tabulate_all_championship(self):
504:         championship.tabulate_championship(
505:             self.sheetbase.racesheet.values(),
506:             self.sheetbase.startsheet.values(),
507:             self.sheetbase.handicapsheet,
508:             self.seriesbase.seriestab,
509:             self.seriesbase.seriesdivisiontab,
510:             self.seriesbase.seriesscoretab,
511:             self.championshipbase.championship,
512:             self.championshipbase.new_championshipdivision,
513:             self.championshipbase.championshipdivisiontab,
514:             self.championshipbase.new_championshipscore,
515:             self.championshipbase.championshipscoretab,
516:         )
517: 
518:     def tabulate_all_casual(self):
519:         casual.tabulate_casual(
520:             self.sheetbase.racesheet.values(),
521:             self.sheetbase.startsheet.values(),
522:             self.sheetbase.handicapsheet,
523:             self.sheetbase.rcsheet,
524:             self.casualbase.casual,
525:             self.casualbase.new_midfleetscore,
526:             self.casualbase.midfleetscoretab,
527:         )
528: 
529: #### database.py #### python package PyScore.tabulate module database ####